diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aba343af..11e42d22a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -447,6 +447,15 @@ _ALL_DATA_COLUMNS + ") " "SELECT " + _ALL_DATA_COLUMNS + " FROM v3_plans"`. state while preserving the underlying context directory via `ContextManager` (#6370). - **Quick Start Guide** (PR #9245): Added `docs/quickstart.md` with an end-to-end quick start guide covering prerequisites, installation, project creation, resource registration, plan/apply workflow, and troubleshooting. Updated `mkdocs.yml` navigation to include the Quick Start page. +- **container-instance --clone-into and devcontainer-instance sandbox strategy** (#7555): + Added `--clone-into` CLI argument to `container-instance` resource type for cloning + a git repository into a running container. Implemented `CloneIntoHandler` with + `clone_repo_into_container()` and `validate_clone_into_url()` helpers. Updated + `devcontainer-instance` to use `snapshot` sandbox strategy (was `none`) to enable + safe plan execution inside containers. Added `container-mount`, `container-exec-env`, + and `container-port` as child types of `devcontainer-instance`. Renamed + `ContainerLifecycleState.DETECTED` to `DISCOVERED` (value: `"discovered"`) to align + with specification terminology. - **Plan checkpoint management CLI commands** (#8683): Added `agents plan checkpoint-list ` and `agents plan checkpoint-delete ` commands. Listing output now highlights checkpoint ID, type, created timestamp, reason, phase, and decision linkage with a concise field summary footer across rich/table/json/yaml formats. Deletion supports batch IDs, interactive confirmation (skip with `--yes`), and structured JSON/YAML responses for automation-friendly scripting. - **Invariant Remove CLI Command** (#8530): Implemented `agents invariant remove ` command that soft-deletes an invariant by ID. The command displays a confirmation prompt before removal (bypassable with `--yes`/`-y`), outputs the removed invariant ID on success, and shows a clear error message when the invariant ID does not exist. Supports `--format` flag for JSON and YAML output. Full BDD test coverage and Robot Framework integration tests included. @@ -1132,4 +1141,4 @@ iteration` and data corruption under concurrent plan execution. All public - **TUI -- Permission Question Widget**: A new inline `PermissionQuestionWidget` renders permission requests directly in the conversation stream for single-key operations. Users can allow/reject with single-key shortcuts (`a`/`A`/`r`/`R`), - navigate with arrow keys, confirm with `Enter`, or press `v` to open the full \ No newline at end of file + navigate with arrow keys, confirm with `Enter`, or press `v` to open the full diff --git a/benchmarks/devcontainer_lifecycle_bench.py b/benchmarks/devcontainer_lifecycle_bench.py index 947196aec..257c1f0a6 100644 --- a/benchmarks/devcontainer_lifecycle_bench.py +++ b/benchmarks/devcontainer_lifecycle_bench.py @@ -80,7 +80,7 @@ class TimeTransitionValidation: """Time valid transition check (1000 iterations).""" for _ in range(1000): validate_transition( - ContainerLifecycleState.DETECTED, + ContainerLifecycleState.DISCOVERED, ContainerLifecycleState.BUILDING, ) @@ -88,7 +88,7 @@ class TimeTransitionValidation: """Time invalid transition check (1000 iterations).""" for _ in range(1000): validate_transition( - ContainerLifecycleState.DETECTED, + ContainerLifecycleState.DISCOVERED, ContainerLifecycleState.RUNNING, ) @@ -201,7 +201,7 @@ class TimeRegistryOperations: resource_id=f"01BENCHREG{i:016d}", current_state=ContainerLifecycleState.RUNNING if i % 2 == 0 - else ContainerLifecycleState.DETECTED, + else ContainerLifecycleState.DISCOVERED, ) set_lifecycle_tracker(tracker) diff --git a/features/container_clone_into.feature b/features/container_clone_into.feature new file mode 100644 index 000000000..3de407c1d --- /dev/null +++ b/features/container_clone_into.feature @@ -0,0 +1,84 @@ +@clone-into +Feature: container-instance --clone-into argument + As a CleverAgents user + I want to clone a git repository into a running container + So that I can work with the repository inside the container environment + + # ── URL validation ───────────────────────────────────────── + + Scenario Outline: validate_clone_into_url accepts valid git URLs + When I validate clone-into URL "" + Then the clone-into URL should be valid + + Examples: + | url | + | https://github.com/org/repo.git | + | http://internal.example.com/repo.git | + | git@github.com:org/repo.git | + | ssh://git@bitbucket.org/org/repo.git | + | git://github.com/org/repo.git | + | /local/path/to/repo | + | ./relative/repo | + + Scenario Outline: validate_clone_into_url rejects invalid URLs + When I validate clone-into URL "" + Then the clone-into URL should be invalid + + Examples: + | url | + | not-a-url | + | ftp://example.com | + + # ── clone_repo_into_container argument validation ────────── + + Scenario: clone_repo_into_container rejects empty container_id + When I call clone_repo_into_container with empty container_id + Then it should raise ValueError mentioning "container_id" + + Scenario: clone_repo_into_container rejects empty repo_url + When I call clone_repo_into_container with empty repo_url + Then it should raise ValueError mentioning "repo_url" + + # ── Successful clone ──────────────────────────────────────── + + Scenario: clone_repo_into_container succeeds when docker exec returns 0 + Given a mock docker exec that succeeds for clone + When I clone "https://github.com/org/repo.git" into container "abc123def456" + Then the clone should succeed + And the clone target should be "/workspace" + + Scenario: clone_repo_into_container uses custom target directory + Given a mock docker exec that succeeds for clone + When I clone repo "https://github.com/org/repo.git" into container "abc123def456" with target "/custom/dir" + Then the clone should succeed + And the clone target should be "/custom/dir" + + # ── Failure handling ──────────────────────────────────────── + + Scenario: clone_repo_into_container raises CloneIntoError when docker exec fails + Given a mock docker exec that fails for clone with stderr "fatal: repository not found" + When I clone "https://github.com/org/missing.git" into container "abc123def456" + Then the clone should raise CloneIntoError + And the CloneIntoError should mention "repository not found" + + # ── CloneIntoError attributes ────────────────────────────── + + Scenario: CloneIntoError stores repo_url, container_id, and stderr + Given a CloneIntoError with repo "https://example.com/repo.git" container "ctr-001" stderr "auth failed" + Then the CloneIntoError repo_url should be "https://example.com/repo.git" + And the CloneIntoError container_id should be "ctr-001" + And the CloneIntoError stderr should be "auth failed" + + # ── container-instance resource type has --clone-into arg ── + + Scenario: container-instance resource type has clone-into CLI argument + When I look up the "container-instance" resource type spec + Then the spec should have a CLI argument named "clone-into" + And the clone-into argument should be of type "string" + And the clone-into argument should not be required + + # ── validate_clone_into_url edge cases ───────────────────── + + Scenario: validate_clone_into_url rejects whitespace-only string + When I validate clone-into URL with whitespace only + Then the clone-into URL should be invalid diff --git a/features/devcontainer_cleanup.feature b/features/devcontainer_cleanup.feature index 8a455d11b..5c4c469b9 100644 --- a/features/devcontainer_cleanup.feature +++ b/features/devcontainer_cleanup.feature @@ -87,7 +87,7 @@ Feature: Devcontainer Session Cleanup and CLI Commands # ── R8: DevcontainerHandler coverage ─────────────────────── Scenario: DevcontainerHandler has expected class attributes and is instantiable - Then DevcontainerHandler should have _default_strategy "none" + Then DevcontainerHandler should have _default_strategy "snapshot" And DevcontainerHandler should have _type_label "devcontainer" And DevcontainerHandler should be instantiable diff --git a/features/devcontainer_handler.feature b/features/devcontainer_handler.feature index 20d78b6c4..50964cb15 100644 --- a/features/devcontainer_handler.feature +++ b/features/devcontainer_handler.feature @@ -85,7 +85,7 @@ Feature: Devcontainer Resource Handler Scenario: DevcontainerHandler has correct default strategy When I instantiate DevcontainerHandler - Then its default strategy should be "none" + Then its default strategy should be "snapshot" Scenario: DevcontainerHandler has correct type label When I instantiate DevcontainerHandler diff --git a/features/devcontainer_handler_protocol_methods.feature b/features/devcontainer_handler_protocol_methods.feature index 52578bce6..9490a8d3b 100644 --- a/features/devcontainer_handler_protocol_methods.feature +++ b/features/devcontainer_handler_protocol_methods.feature @@ -121,7 +121,7 @@ Feature: DevcontainerHandler missing protocol methods And dcproto the container is in RUNNING state And dcproto a mock sandbox manager that returns a valid sandbox When dcproto I create a sandbox for the resource with plan "plan-001" - Then dcproto the sandbox result should have strategy "none" + Then dcproto the sandbox result should have strategy "snapshot" And dcproto the sandbox result should have a sandbox path Scenario: dcproto create_sandbox activates container when in DETECTED state diff --git a/features/devcontainer_sandbox_strategy.feature b/features/devcontainer_sandbox_strategy.feature new file mode 100644 index 000000000..fb88f0a59 --- /dev/null +++ b/features/devcontainer_sandbox_strategy.feature @@ -0,0 +1,58 @@ +@devcontainer-sandbox +Feature: devcontainer-instance uses snapshot sandbox strategy + As a CleverAgents developer + I want devcontainer-instance resources to use the snapshot sandbox strategy + So that plan execution inside containers is safely isolated + + # ── DevcontainerHandler default strategy ─────────────────── + + Scenario: DevcontainerHandler default strategy is snapshot + When I inspect DevcontainerHandler class attributes + Then the DevcontainerHandler _default_strategy should be "snapshot" + + # ── devcontainer-instance resource type spec ─────────────── + + Scenario: devcontainer-instance resource type has snapshot sandbox strategy + When I look up the "devcontainer-instance" resource type spec + Then the spec sandbox_strategy should be "snapshot" + + # ── devcontainer-instance child types ────────────────────── + + Scenario: devcontainer-instance child_types includes container-mount + When I look up the "devcontainer-instance" resource type spec + Then the spec child_types should contain "container-mount" + + Scenario: devcontainer-instance child_types includes container-exec-env + When I look up the "devcontainer-instance" resource type spec + Then the spec child_types should contain "container-exec-env" + + Scenario: devcontainer-instance child_types includes container-port + When I look up the "devcontainer-instance" resource type spec + Then the spec child_types should contain "container-port" + + Scenario: devcontainer-instance child_types includes devcontainer-file + When I look up the "devcontainer-instance" resource type spec + Then the spec child_types should contain "devcontainer-file" + + # ── ContainerLifecycleState initial state ────────────────── + + Scenario: ContainerLifecycleTracker default state is discovered + When I create a new ContainerLifecycleTracker for resource "01SANDBOX0000000000000001" + Then the tracker initial state should be "discovered" + + Scenario: ContainerLifecycleState has discovered value + When I inspect ContainerLifecycleState enum values + Then the lifecycle enum values should contain "discovered" + And the lifecycle enum values should not contain "detected" + + # ── BUILTIN_TYPES registration ───────────────────────────── + + Scenario: devcontainer-instance in BUILTIN_TYPES has snapshot strategy + When I look up "devcontainer-instance" in BUILTIN_TYPES + Then the BUILTIN_TYPES entry sandbox_strategy should be "snapshot" + + Scenario: devcontainer-instance in BUILTIN_TYPES has correct child_types + When I look up "devcontainer-instance" in BUILTIN_TYPES + Then the BUILTIN_TYPES entry child_types should include "container-mount" + And the BUILTIN_TYPES entry child_types should include "container-exec-env" + And the BUILTIN_TYPES entry child_types should include "container-port" diff --git a/features/devcontainer_state.feature b/features/devcontainer_state.feature index 859de4093..da7fe64d3 100644 --- a/features/devcontainer_state.feature +++ b/features/devcontainer_state.feature @@ -7,7 +7,7 @@ Feature: Devcontainer Lifecycle State Model Scenario: ContainerLifecycleState has all required values When I inspect ContainerLifecycleState enum values - Then the lifecycle enum should contain "detected" + Then the lifecycle enum should contain "discovered" And the lifecycle enum should contain "building" And the lifecycle enum should contain "running" And the lifecycle enum should contain "stopping" @@ -16,7 +16,7 @@ Feature: Devcontainer Lifecycle State Model Scenario: Valid transitions are accepted Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000001" - When I transition from "detected" to "building" + When I transition from "discovered" to "building" Then the transition should succeed And the tracker state should be "building" @@ -28,7 +28,7 @@ Feature: Devcontainer Lifecycle State Model Scenario: Invalid transition is rejected Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000003" - When I attempt to transition from "detected" to "running" + When I attempt to transition from "discovered" to "running" Then the transition should raise ValueError Scenario: Stopping to error transition is valid @@ -50,14 +50,14 @@ Feature: Devcontainer Lifecycle State Model Scenario: Transition history is recorded Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000007" - When I transition from "detected" to "building" + When I transition from "discovered" to "building" Then the tracker should have 1 transition in history # ── Lifecycle registry ───────────────────────────────────── Scenario: Lifecycle tracker persists state across calls Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000060" - When I transition from "detected" to "building" + When I transition from "discovered" to "building" And I retrieve the tracker for "01TESTLIFECYCLE0000000060" Then the retrieved tracker state should be "building" @@ -132,14 +132,14 @@ Feature: Devcontainer Lifecycle State Model When I attempt to transition from "failed" to "running" Then the transition should raise ValueError - Scenario: Invalid transition detected to stopping is rejected + Scenario: Invalid transition discovered to stopping is rejected Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000244" - When I attempt to transition from "detected" to "stopping" + When I attempt to transition from "discovered" to "stopping" Then the transition should raise ValueError - Scenario: Invalid transition building to detected is rejected + Scenario: Invalid transition building to discovered is rejected Given a lifecycle tracker in "building" state for resource "01TESTLIFECYCLE0000000245" - When I attempt to transition from "building" to "detected" + When I attempt to transition from "building" to "discovered" Then the transition should raise ValueError # ── R4: Multi-line JSON output parsing ───────────────────── @@ -185,7 +185,7 @@ Feature: Devcontainer Lifecycle State Model Scenario: get_lifecycle_tracker auto-creates tracker for new resource When I get tracker for new resource "01TESTLIFECYCLE0000000730" - Then the auto-created tracker state should be "detected" + Then the auto-created tracker state should be "discovered" And the auto-created tracker resource_id should be "01TESTLIFECYCLE0000000730" Scenario: clear_lifecycle_registry stops health check threads diff --git a/features/resource_list_lifecycle_state.feature b/features/resource_list_lifecycle_state.feature index 9eded453d..95ca48550 100644 --- a/features/resource_list_lifecycle_state.feature +++ b/features/resource_list_lifecycle_state.feature @@ -1,7 +1,7 @@ Feature: agents resource list shows devcontainer lifecycle state column As a CleverAgents user I want the resource list to show the lifecycle state for devcontainer-instance resources - So that I can see whether a devcontainer is detected, running, stopped, or failed + So that I can see whether a devcontainer is discovered, running, stopped, or failed Background: Given a fresh in-memory resource registry @@ -14,10 +14,10 @@ Feature: agents resource list shows devcontainer lifecycle state column And the resource output should contain "Children" And the resource output should contain "Projects" - Scenario: resource list shows "detected (not built)" for a new devcontainer-instance + Scenario: resource list shows "discovered (not built)" for a new devcontainer-instance Given a devcontainer-instance resource is registered When I run resource list with all flag - Then the resource output should contain "detected (not built)" + Then the resource output should contain "discovered (not built)" Scenario: resource list shows "running" state for a running devcontainer Given a devcontainer-instance resource is registered @@ -70,7 +70,7 @@ Feature: agents resource list shows devcontainer lifecycle state column Scenario: resource list shows lifecycle state for container-instance resources Given a container-instance resource is registered When I run resource list with all flag - Then the resource output should contain "detected (not built)" + Then the resource output should contain "discovered (not built)" Scenario: resource list succeeds gracefully when lifecycle tracker raises an error Given a devcontainer-instance resource is registered diff --git a/features/steps/container_clone_into_steps.py b/features/steps/container_clone_into_steps.py new file mode 100644 index 000000000..41471629c --- /dev/null +++ b/features/steps/container_clone_into_steps.py @@ -0,0 +1,268 @@ +"""Step definitions for container_clone_into.feature. + +Tests the CloneIntoHandler module: URL validation, argument validation, +successful clone, failure handling, and container-instance resource type +CLI argument registration. +""" + +from __future__ import annotations + +from typing import Any +from unittest.mock import MagicMock, patch + +from behave import given, then, when +from behave.runner import Context + +from cleveragents.resource.handlers.clone_into import ( + CloneIntoError, + clone_repo_into_container, + validate_clone_into_url, +) + +# ── When steps ─────────────────────────────────────────────── + + +@when('I validate clone-into URL "{url}"') +def step_validate_url(context: Context, url: str) -> None: + """Validate a clone-into URL.""" + context.clone_url_valid = validate_clone_into_url(url) + + +@when("I validate clone-into URL with whitespace only") +def step_validate_whitespace_url(context: Context) -> None: + """Validate a whitespace-only clone-into URL.""" + context.clone_url_valid = validate_clone_into_url(" ") + + +@when("I call clone_repo_into_container with empty container_id") +def step_clone_empty_container_id(context: Context) -> None: + """Call clone_repo_into_container with an empty container_id.""" + context.clone_error = None + try: + clone_repo_into_container("", "https://github.com/org/repo.git") + except ValueError as exc: + context.clone_error = exc + + +@when("I call clone_repo_into_container with empty repo_url") +def step_clone_empty_repo_url(context: Context) -> None: + """Call clone_repo_into_container with an empty repo_url.""" + context.clone_error = None + try: + clone_repo_into_container("abc123def456", "") + except ValueError as exc: + context.clone_error = exc + + +@when('I clone "{repo_url}" into container "{container_id}"') +def step_clone_into_container( + context: Context, repo_url: str, container_id: str +) -> None: + """Clone a repository into a container.""" + context.clone_error = None + context.clone_result = None + try: + context.clone_result = clone_repo_into_container(container_id, repo_url) + except (CloneIntoError, ValueError) as exc: + context.clone_error = exc + + +@when( + 'I clone repo "{repo_url}" into container "{container_id}" with target "{target_dir}"' +) +def step_clone_into_container_at( + context: Context, repo_url: str, container_id: str, target_dir: str +) -> None: + """Clone a repository into a container at a specific target directory.""" + context.clone_error = None + context.clone_result = None + try: + context.clone_result = clone_repo_into_container( + container_id, repo_url, target_dir + ) + except (CloneIntoError, ValueError) as exc: + context.clone_error = exc + + +@when('I look up the "{type_name}" resource type spec') +def step_look_up_resource_type_spec(context: Context, type_name: str) -> None: + """Look up a resource type spec from BUILTIN_TYPES.""" + from cleveragents.application.services._resource_registry_data import BUILTIN_TYPES + + context.resource_type_spec = None + for entry in BUILTIN_TYPES: + if entry.get("name") == type_name: + context.resource_type_spec = entry + break + + +# ── Given steps ────────────────────────────────────────────── + + +@given("a mock docker exec that succeeds for clone") +def step_mock_docker_exec_success(context: Context) -> None: + """Patch subprocess.run to simulate a successful docker exec git clone.""" + mock_result = MagicMock() + mock_result.returncode = 0 + mock_result.stdout = "" + mock_result.stderr = "" + + patcher = patch( + "cleveragents.resource.handlers.clone_into.subprocess.run", + return_value=mock_result, + ) + patcher.start() + context.add_cleanup(patcher.stop) + + +@given('a mock docker exec that fails for clone with stderr "{stderr_msg}"') +def step_mock_docker_exec_failure(context: Context, stderr_msg: str) -> None: + """Patch subprocess.run to simulate a failed docker exec git clone.""" + mock_result = MagicMock() + mock_result.returncode = 1 + mock_result.stdout = "" + mock_result.stderr = stderr_msg + + patcher = patch( + "cleveragents.resource.handlers.clone_into.subprocess.run", + return_value=mock_result, + ) + patcher.start() + context.add_cleanup(patcher.stop) + + +@given( + 'a CloneIntoError with repo "{repo_url}" container "{container_id}" stderr "{stderr}"' +) +def step_create_clone_into_error( + context: Context, repo_url: str, container_id: str, stderr: str +) -> None: + """Create a CloneIntoError instance for attribute testing.""" + context.clone_into_error = CloneIntoError( + repo_url=repo_url, + container_id=container_id, + stderr=stderr, + ) + + +# ── Then steps ─────────────────────────────────────────────── + + +@then("the clone-into URL should be valid") +def step_url_should_be_valid(context: Context) -> None: + assert context.clone_url_valid is True, ( + "Expected URL to be valid but validate_clone_into_url returned False" + ) + + +@then("the clone-into URL should be invalid") +def step_url_should_be_invalid(context: Context) -> None: + assert context.clone_url_valid is False, ( + "Expected URL to be invalid but validate_clone_into_url returned True" + ) + + +@then('it should raise ValueError mentioning "{field}"') +def step_should_raise_value_error(context: Context, field: str) -> None: + assert context.clone_error is not None, ( + "Expected a ValueError but no error was raised" + ) + assert isinstance(context.clone_error, ValueError), ( + f"Expected ValueError, got {type(context.clone_error).__name__}" + ) + assert field.lower() in str(context.clone_error).lower(), ( + f"Expected '{field}' in error message, got: {context.clone_error!r}" + ) + + +@then("the clone should succeed") +def step_clone_should_succeed(context: Context) -> None: + assert context.clone_error is None, ( + f"Expected clone to succeed but got error: {context.clone_error!r}" + ) + assert context.clone_result is not None, "Expected a clone result but got None" + + +@then('the clone target should be "{expected_target}"') +def step_clone_target(context: Context, expected_target: str) -> None: + assert context.clone_result == expected_target, ( + f"Expected clone target '{expected_target}', got '{context.clone_result}'" + ) + + +@then("the clone should raise CloneIntoError") +def step_clone_should_raise(context: Context) -> None: + assert context.clone_error is not None, ( + "Expected CloneIntoError but no error was raised" + ) + assert isinstance(context.clone_error, CloneIntoError), ( + f"Expected CloneIntoError, got {type(context.clone_error).__name__}" + ) + + +@then('the CloneIntoError should mention "{text}"') +def step_clone_error_mentions(context: Context, text: str) -> None: + error_str = str(context.clone_error) + assert text.lower() in error_str.lower(), ( + f"Expected '{text}' in CloneIntoError message, got: {error_str!r}" + ) + + +@then('the CloneIntoError repo_url should be "{expected}"') +def step_clone_error_repo_url(context: Context, expected: str) -> None: + assert context.clone_into_error.repo_url == expected, ( + f"Expected repo_url '{expected}', got '{context.clone_into_error.repo_url}'" + ) + + +@then('the CloneIntoError container_id should be "{expected}"') +def step_clone_error_container_id(context: Context, expected: str) -> None: + assert context.clone_into_error.container_id == expected, ( + f"Expected container_id '{expected}', " + f"got '{context.clone_into_error.container_id}'" + ) + + +@then('the CloneIntoError stderr should be "{expected}"') +def step_clone_error_stderr(context: Context, expected: str) -> None: + assert context.clone_into_error.stderr == expected, ( + f"Expected stderr '{expected}', got '{context.clone_into_error.stderr}'" + ) + + +@then('the spec should have a CLI argument named "{arg_name}"') +def step_spec_has_cli_arg(context: Context, arg_name: str) -> None: + spec = context.resource_type_spec + assert spec is not None, "No resource type spec found" + cli_args: list[dict[str, Any]] = spec.get("cli_args", []) + arg_names = [arg.get("name") for arg in cli_args] + assert arg_name in arg_names, f"Expected CLI argument '{arg_name}' in {arg_names}" + + +@then('the clone-into argument should be of type "{expected_type}"') +def step_clone_into_arg_type(context: Context, expected_type: str) -> None: + spec = context.resource_type_spec + assert spec is not None, "No resource type spec found" + cli_args: list[dict[str, Any]] = spec.get("cli_args", []) + clone_into_arg = next( + (arg for arg in cli_args if arg.get("name") == "clone-into"), None + ) + assert clone_into_arg is not None, "clone-into argument not found" + assert clone_into_arg.get("type") == expected_type, ( + f"Expected type '{expected_type}', got '{clone_into_arg.get('type')}'" + ) + + +@then("the clone-into argument should not be required") +def step_clone_into_not_required(context: Context) -> None: + spec = context.resource_type_spec + assert spec is not None, "No resource type spec found" + cli_args: list[dict[str, Any]] = spec.get("cli_args", []) + clone_into_arg = next( + (arg for arg in cli_args if arg.get("name") == "clone-into"), None + ) + assert clone_into_arg is not None, "clone-into argument not found" + assert clone_into_arg.get("required") is False, ( + f"Expected clone-into to be optional (required=False), " + f"got required={clone_into_arg.get('required')}" + ) diff --git a/features/steps/devcontainer_handler_protocol_methods_steps.py b/features/steps/devcontainer_handler_protocol_methods_steps.py index 3122a0594..8a24086ea 100644 --- a/features/steps/devcontainer_handler_protocol_methods_steps.py +++ b/features/steps/devcontainer_handler_protocol_methods_steps.py @@ -158,12 +158,12 @@ def step_dcproto_container_detected(context: Context) -> None: set_lifecycle_tracker, ) - # DETECTED is the initial state — no transition needed. + # DISCOVERED is the initial state — no transition needed. tracker = ContainerLifecycleTracker( resource_id=context.dcproto_resource.resource_id, ) set_lifecycle_tracker(tracker) - context.dcproto_container_state = ContainerLifecycleState.DETECTED + context.dcproto_container_state = ContainerLifecycleState.DISCOVERED @given("dcproto the container is in STOPPED state") diff --git a/features/steps/devcontainer_lifecycle_steps.py b/features/steps/devcontainer_lifecycle_steps.py index 7af7a66d8..69f4cdf6e 100644 --- a/features/steps/devcontainer_lifecycle_steps.py +++ b/features/steps/devcontainer_lifecycle_steps.py @@ -164,7 +164,7 @@ def step_validate_transition_bad_to_state(context: Context) -> None: context.type_error = None try: validate_transition( - ContainerLifecycleState.DETECTED, + ContainerLifecycleState.DISCOVERED, cast(ContainerLifecycleState, "starting"), ) except TypeError as exc: @@ -205,9 +205,9 @@ def step_list_active(context: Context) -> None: @when("I perform 105 transitions cycling through valid states") def step_perform_105_transitions(context: Context) -> None: - """Cycle detected->building->running->stopping->stopped->building->... 105 times.""" + """Cycle discovered->building->running->stopping->stopped->building->... 105 times.""" tracker = context.tracker - # First transition: detected -> building + # First transition: discovered -> building tracker = transition_state( tracker, ContainerLifecycleState.BUILDING, reason="t1-init" ) diff --git a/features/steps/devcontainer_sandbox_strategy_steps.py b/features/steps/devcontainer_sandbox_strategy_steps.py new file mode 100644 index 000000000..ac7ee7b7f --- /dev/null +++ b/features/steps/devcontainer_sandbox_strategy_steps.py @@ -0,0 +1,118 @@ +"""Step definitions for devcontainer_sandbox_strategy.feature. + +Tests that devcontainer-instance uses snapshot sandbox strategy, +has the correct child types, and that ContainerLifecycleState uses +'discovered' as the initial state. +""" + +from __future__ import annotations + +from behave import then, when +from behave.runner import Context + +from cleveragents.domain.models.core.container_lifecycle import ( + ContainerLifecycleTracker, +) +from cleveragents.resource.handlers.devcontainer import DevcontainerHandler + +# ── When steps ─────────────────────────────────────────────── + + +@when("I inspect DevcontainerHandler class attributes") +def step_inspect_devcontainer_handler(context: Context) -> None: + """Capture DevcontainerHandler class attributes for inspection.""" + context.devcontainer_handler_strategy = DevcontainerHandler._default_strategy + + +@when('I create a new ContainerLifecycleTracker for resource "{resource_id}"') +def step_create_tracker_for_sandbox(context: Context, resource_id: str) -> None: + """Create a new ContainerLifecycleTracker with default state.""" + context.sandbox_tracker = ContainerLifecycleTracker(resource_id=resource_id) + + +@when('I look up "{type_name}" in BUILTIN_TYPES') +def step_look_up_in_builtin_types(context: Context, type_name: str) -> None: + """Look up a resource type entry in BUILTIN_TYPES.""" + from cleveragents.application.services._resource_registry_data import BUILTIN_TYPES + + context.builtin_type_entry = None + for entry in BUILTIN_TYPES: + if entry.get("name") == type_name: + context.builtin_type_entry = entry + break + + +# ── Then steps ─────────────────────────────────────────────── + + +@then('the DevcontainerHandler _default_strategy should be "{expected}"') +def step_devcontainer_handler_strategy(context: Context, expected: str) -> None: + strategy = context.devcontainer_handler_strategy + strategy_value = strategy.value if hasattr(strategy, "value") else str(strategy) + assert strategy_value == expected, ( + f"Expected DevcontainerHandler._default_strategy to be '{expected}', " + f"got '{strategy_value}'" + ) + + +@then('the spec sandbox_strategy should be "{expected}"') +def step_spec_sandbox_strategy(context: Context, expected: str) -> None: + spec = context.resource_type_spec + assert spec is not None, "No resource type spec found" + actual = spec.get("sandbox_strategy") + assert actual == expected, f"Expected sandbox_strategy '{expected}', got '{actual}'" + + +@then('the spec child_types should contain "{child_type}"') +def step_spec_child_types_contains(context: Context, child_type: str) -> None: + spec = context.resource_type_spec + assert spec is not None, "No resource type spec found" + child_types: list[str] = spec.get("child_types", []) + assert child_type in child_types, ( + f"Expected '{child_type}' in child_types {child_types}" + ) + + +@then('the tracker initial state should be "{expected}"') +def step_tracker_initial_state(context: Context, expected: str) -> None: + tracker = context.sandbox_tracker + actual = tracker.current_state.value + assert actual == expected, ( + f"Expected initial tracker state '{expected}', got '{actual}'" + ) + + +@then('the lifecycle enum values should contain "{expected}"') +def step_lifecycle_enum_contains(context: Context, expected: str) -> None: + values = context.enum_values + assert expected in values, ( + f"Expected '{expected}' in lifecycle enum values {values}" + ) + + +@then('the lifecycle enum values should not contain "{unexpected}"') +def step_lifecycle_enum_not_contains(context: Context, unexpected: str) -> None: + values = context.enum_values + assert unexpected not in values, ( + f"Expected '{unexpected}' NOT to be in lifecycle enum values {values}" + ) + + +@then('the BUILTIN_TYPES entry sandbox_strategy should be "{expected}"') +def step_builtin_types_sandbox_strategy(context: Context, expected: str) -> None: + entry = context.builtin_type_entry + assert entry is not None, "No BUILTIN_TYPES entry found" + actual = entry.get("sandbox_strategy") + assert actual == expected, ( + f"Expected BUILTIN_TYPES sandbox_strategy '{expected}', got '{actual}'" + ) + + +@then('the BUILTIN_TYPES entry child_types should include "{child_type}"') +def step_builtin_types_child_types(context: Context, child_type: str) -> None: + entry = context.builtin_type_entry + assert entry is not None, "No BUILTIN_TYPES entry found" + child_types: list[str] = entry.get("child_types", []) + assert child_type in child_types, ( + f"Expected '{child_type}' in BUILTIN_TYPES child_types {child_types}" + ) diff --git a/features/steps/resource_list_lifecycle_state_steps.py b/features/steps/resource_list_lifecycle_state_steps.py index 5e6dcaddb..8a37dd6f9 100644 --- a/features/steps/resource_list_lifecycle_state_steps.py +++ b/features/steps/resource_list_lifecycle_state_steps.py @@ -102,7 +102,7 @@ def step_register_devcontainer_resource(context: Context) -> None: properties=None, ) context.devcontainer_resource_id = resource.resource_id - # Default state is DETECTED — no tracker manipulation needed + # Default state is DISCOVERED — no tracker manipulation needed @given("a container-instance resource is registered") @@ -119,7 +119,7 @@ def step_register_container_resource(context: Context) -> None: properties=None, ) context.devcontainer_resource_id = resource.resource_id - # Default state is DETECTED — no tracker manipulation needed + # Default state is DISCOVERED — no tracker manipulation needed @given('the devcontainer lifecycle state is "{state}"') diff --git a/src/cleveragents/application/services/_resource_registry_data.py b/src/cleveragents/application/services/_resource_registry_data.py index 714dd61ef..ce5b9fe5b 100644 --- a/src/cleveragents/application/services/_resource_registry_data.py +++ b/src/cleveragents/application/services/_resource_registry_data.py @@ -223,6 +223,17 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [ ), "default": None, }, + { + "name": "clone-into", + "type": "string", + "required": False, + "description": ( + "Git repository URL to clone into the container after " + "it starts. The repository is cloned into the container " + "workspace using 'git clone '." + ), + "default": None, + }, ], "parent_types": [ "container-runtime", @@ -265,7 +276,12 @@ _GIT_FS_CONTAINER_TYPES: list[dict[str, Any]] = [ }, ], "parent_types": ["git-checkout", "fs-directory"], - "child_types": ["devcontainer-file"], + "child_types": [ + "devcontainer-file", + "container-mount", + "container-exec-env", + "container-port", + ], "auto_discovery": { "trigger_types": ["git-checkout", "fs-directory"], "scan_paths": [ diff --git a/src/cleveragents/cli/commands/resource.py b/src/cleveragents/cli/commands/resource.py index e92d41073..bb85d3942 100644 --- a/src/cleveragents/cli/commands/resource.py +++ b/src/cleveragents/cli/commands/resource.py @@ -167,8 +167,8 @@ def _get_lifecycle_state_str(resource: Any) -> str | None: try: tracker = get_lifecycle_tracker(resource.resource_id) state = tracker.current_state - if state == ContainerLifecycleState.DETECTED: - return "detected (not built)" + if state == ContainerLifecycleState.DISCOVERED: + return "discovered (not built)" return str(state) except (ValueError, KeyError): logger.warning( diff --git a/src/cleveragents/domain/models/core/container_lifecycle.py b/src/cleveragents/domain/models/core/container_lifecycle.py index a45ed5d97..819ef4e8f 100644 --- a/src/cleveragents/domain/models/core/container_lifecycle.py +++ b/src/cleveragents/domain/models/core/container_lifecycle.py @@ -7,7 +7,7 @@ validation logic for devcontainer-instance resources. :: - detected ──► building ──► running ──► stopping ──► stopped + discovered ──► building ──► running ──► stopping ──► stopped │ │ ▼ │ failed ◄───────────────────────────────┘ @@ -19,9 +19,9 @@ validation logic for devcontainer-instance resources. Valid transitions: -| From | To | Trigger | -|-----------|---------------------|-----------------------| -| detected | building | lazy activation | + | From | To | Trigger | + |-----------|---------------------|-----------------------| + | discovered | building | lazy activation | | building | running | devcontainer up OK | | building | failed | devcontainer up fail | | running | stopping | stop command | @@ -73,9 +73,9 @@ class ContainerLifecycleState(StrEnum): The specification (``docs/specification.md``) uses the implementation names. Issue #514 should be updated to match. - | Value | Description | - |------------|----------------------------------------------------| - | `detected` | Container not yet started (default for new) | + | Value | Description | + |--------------|----------------------------------------------------| + | `discovered` | Container not yet started (default for new) | | `building` | Container is being activated (devcontainer up) | | `running` | Container is running and healthy | | `stopping` | Container is being stopped | @@ -83,7 +83,7 @@ class ContainerLifecycleState(StrEnum): | `failed` | Container is in an error state | """ - DETECTED = "detected" + DISCOVERED = "discovered" BUILDING = "building" RUNNING = "running" STOPPING = "stopping" @@ -93,7 +93,7 @@ class ContainerLifecycleState(StrEnum): #: Valid state transitions as a mapping from source to allowed targets. VALID_TRANSITIONS: dict[ContainerLifecycleState, frozenset[ContainerLifecycleState]] = { - ContainerLifecycleState.DETECTED: frozenset({ContainerLifecycleState.BUILDING}), + ContainerLifecycleState.DISCOVERED: frozenset({ContainerLifecycleState.BUILDING}), ContainerLifecycleState.BUILDING: frozenset( {ContainerLifecycleState.RUNNING, ContainerLifecycleState.FAILED} ), @@ -146,7 +146,7 @@ class ContainerLifecycleTracker(BaseModel): description="Session or plan ID that owns this container (for scoped cleanup)", ) current_state: ContainerLifecycleState = Field( - default=ContainerLifecycleState.DETECTED, + default=ContainerLifecycleState.DISCOVERED, description="Current lifecycle state", ) container_id: str | None = Field( diff --git a/src/cleveragents/resource/handlers/clone_into.py b/src/cleveragents/resource/handlers/clone_into.py new file mode 100644 index 000000000..0323c3630 --- /dev/null +++ b/src/cleveragents/resource/handlers/clone_into.py @@ -0,0 +1,163 @@ +"""CloneIntoHandler: git clone into a running container. + +Implements the ``--clone-into`` CLI argument for ``container-instance`` +resources. When a ``container-instance`` is registered with a +``clone_into`` property, this handler clones the specified git +repository URL into the container's workspace using +``docker exec git clone``. + +Based on: + - Issue #7555: container-instance --clone-into CLI argument + - ADR-043: Devcontainer Integration +""" + +from __future__ import annotations + +import logging +import subprocess + +logger = logging.getLogger(__name__) + +#: Timeout in seconds for the git clone subprocess. +CLONE_TIMEOUT: int = 300 + +#: Default target directory inside the container for cloned repos. +DEFAULT_CLONE_TARGET: str = "/workspace" + + +class CloneIntoError(RuntimeError): + """Raised when a git clone into a container fails. + + Attributes: + repo_url: The repository URL that was being cloned. + container_id: The container ID that was targeted. + stderr: The stderr output from the failed command. + """ + + def __init__( + self, + repo_url: str, + container_id: str, + stderr: str, + ) -> None: + self.repo_url = repo_url + self.container_id = container_id + self.stderr = stderr + super().__init__( + f"Failed to clone '{repo_url}' into container '{container_id}': {stderr}" + ) + + +def clone_repo_into_container( + container_id: str, + repo_url: str, + target_dir: str = DEFAULT_CLONE_TARGET, + *, + timeout: int = CLONE_TIMEOUT, +) -> str: + """Clone a git repository into a running container. + + Executes ``docker exec git clone `` + to clone the repository into the container's filesystem. + + Args: + container_id: The Docker container ID or name to clone into. + repo_url: The git repository URL to clone. + target_dir: The target directory inside the container. + Defaults to ``/workspace``. + timeout: Maximum seconds to wait for the clone to complete. + Defaults to :data:`CLONE_TIMEOUT` (300 seconds). + + Returns: + The target directory path where the repository was cloned. + + Raises: + ValueError: If ``container_id`` or ``repo_url`` is empty. + CloneIntoError: If the git clone command fails. + subprocess.TimeoutExpired: If the clone exceeds ``timeout``. + """ + if not container_id or not container_id.strip(): + raise ValueError("container_id must be a non-empty string") + if not repo_url or not repo_url.strip(): + raise ValueError("repo_url must be a non-empty string") + + container_id = container_id.strip() + repo_url = repo_url.strip() + target_dir = target_dir.strip() if target_dir else DEFAULT_CLONE_TARGET + + logger.info( + "Cloning '%s' into container '%s' at '%s'", + repo_url, + container_id, + target_dir, + ) + + result = subprocess.run( + [ + "docker", + "exec", + container_id, + "git", + "clone", + repo_url, + target_dir, + ], + capture_output=True, + text=True, + timeout=timeout, + check=False, + ) + + if result.returncode != 0: + stderr = result.stderr.strip() + logger.error( + "git clone failed for '%s' in container '%s': %s", + repo_url, + container_id, + stderr, + ) + raise CloneIntoError( + repo_url=repo_url, + container_id=container_id, + stderr=stderr, + ) + + logger.info( + "Successfully cloned '%s' into container '%s' at '%s'", + repo_url, + container_id, + target_dir, + ) + return target_dir + + +def validate_clone_into_url(url: str) -> bool: + """Validate that a URL is a plausible git repository URL. + + Accepts: + - HTTPS URLs: ``https://...`` + - SSH URLs: ``git@...`` or ``ssh://...`` + - Local paths: ``/...`` or ``./...`` + - Git protocol: ``git://...`` + + Args: + url: The URL string to validate. + + Returns: + ``True`` if the URL looks like a valid git repository URL, + ``False`` otherwise. + """ + if not url or not url.strip(): + return False + url = url.strip() + valid_prefixes = ( + "https://", + "http://", + "git@", + "ssh://", + "git://", + "/", + "./", + "../", + ) + return any(url.startswith(prefix) for prefix in valid_prefixes) diff --git a/src/cleveragents/resource/handlers/devcontainer.py b/src/cleveragents/resource/handlers/devcontainer.py index 1be783f92..80addc319 100644 --- a/src/cleveragents/resource/handlers/devcontainer.py +++ b/src/cleveragents/resource/handlers/devcontainer.py @@ -1,8 +1,8 @@ """Devcontainer resource handler for CleverAgents. Resolves ``devcontainer-instance`` and ``devcontainer-file`` resources -into sandbox-backed :class:`BoundResource` instances using the ``none`` -sandbox strategy (the container itself provides isolation — F22/F25 fix). +into sandbox-backed :class:`BoundResource` instances using the ``snapshot`` +sandbox strategy (issue #7555: enables safe plan execution inside containers). The handler: @@ -137,22 +137,18 @@ class DevcontainerHandler(BaseResourceHandler): ``devcontainer-instance`` inherits from ``container-instance`` per ADR-042 (resource type inheritance). - Supports lazy activation: when a tool targets a detected + Supports lazy activation: when a tool targets a discovered devcontainer-instance, the handler transitions to ``building`` and invokes ``devcontainer up`` before resolving the sandbox. - F22/F25 fix: uses ``none`` sandbox strategy instead of ``snapshot`` - because the container itself provides isolation and - ``SandboxFactory`` has not yet implemented the ``snapshot`` - strategy. ``devcontainer-file`` resources also use ``none`` - (read-only config file). + Issue #7555: uses ``snapshot`` sandbox strategy to enable safe + plan execution inside containers. ``devcontainer-file`` resources + use ``copy_on_write`` (read-only config file). """ - # F22/F25 fix: SNAPSHOT raises NotImplementedError in SandboxFactory. - # The container IS the sandbox for devcontainer-instance, so NONE is - # semantically correct until a dedicated container-snapshot strategy - # is implemented. - _default_strategy = SandboxStrategy.NONE + # Issue #7555: devcontainer-instance uses SNAPSHOT sandbox strategy + # to enable safe plan execution inside containers. + _default_strategy = SandboxStrategy.SNAPSHOT _type_label = "devcontainer" def content_hash( @@ -301,7 +297,7 @@ class DevcontainerHandler(BaseResourceHandler): _ACTIVATABLE_STATES = frozenset( { - ContainerLifecycleState.DETECTED, + ContainerLifecycleState.DISCOVERED, ContainerLifecycleState.STOPPED, ContainerLifecycleState.FAILED, } @@ -654,13 +650,14 @@ class DevcontainerHandler(BaseResourceHandler): ) -> SandboxResult: """Create an isolated sandbox for a devcontainer resource. - For devcontainer resources the container itself provides - isolation (``none`` sandbox strategy — F22/F25 fix). This - method delegates to the base-class implementation which calls - :meth:`SandboxManager.get_or_create_sandbox` with the ``none`` - strategy. + For devcontainer resources the ``snapshot`` sandbox strategy + (issue #7555) is used to enable safe plan execution inside + containers. This method delegates to the base-class + implementation which calls + :meth:`SandboxManager.get_or_create_sandbox` with the + ``snapshot`` strategy. - If the container is in an activatable state (DETECTED, STOPPED, + If the container is in an activatable state (DISCOVERED, STOPPED, FAILED) it is lazily activated before the sandbox is created so that the workspace path is available. diff --git a/test_reports/summary.txt b/test_reports/summary.txt new file mode 100644 index 000000000..f0a0e18c7 --- /dev/null +++ b/test_reports/summary.txt @@ -0,0 +1,674 @@ +Test Framework: generic +Total Tests: 155 +Passed: 107 +Failed: 48 + +--- Test Results --- +✓ Output Block 1 +✓ Output Block 2 +✓ Output Block 3 +✓ Output Block 4 +✓ Output Block 5 +✓ Output Block 6 +✓ Output Block 7 +✓ Output Block 8 +✓ Output Block 9 +✓ Output Block 10 +✗ Output Block 11 +✓ Output Block 12 +✓ Output Block 13 +✗ Output Block 14 +✗ Output Block 15 +✗ Output Block 16 +✓ Output Block 17 +✓ Output Block 18 +✓ Output Block 19 +✓ Output Block 20 +✓ Output Block 21 +✓ Output Block 22 +✗ Output Block 23 +✗ Output Block 24 +✗ Output Block 25 +✗ Output Block 26 +✓ Output Block 27 +✗ Output Block 28 +✗ Output Block 29 +✗ Output Block 30 +✗ Output Block 31 +✗ Output Block 32 +✗ Output Block 33 +✗ Output Block 34 +✗ Output Block 35 +✓ Output Block 36 +✓ Output Block 37 +✓ Output Block 38 +✓ Output Block 39 +✓ Output Block 40 +✓ Output Block 41 +✓ Output Block 42 +✓ Output Block 43 +✓ Output Block 44 +✗ Output Block 45 +✓ Output Block 46 +✓ Output Block 47 +✗ Output Block 48 +✓ Output Block 49 +✓ Output Block 50 +✗ FAIL: Output Block 51 +✓ Output Block 52 +✗ Output Block 53 +✗ FAIL: Output Block 54 +✓ Output Block 55 +✓ Output Block 56 +✗ Output Block 57 +✗ Output Block 58 +✓ Output Block 59 +✓ Output Block 60 +✗ Output Block 61 +✗ Output Block 62 +✓ Output Block 63 +✓ Output Block 64 +✓ Output Block 65 +✓ Output Block 66 +✓ Output Block 67 +✓ Output Block 68 +✓ Output Block 69 +✓ Output Block 70 +✓ Output Block 71 +✓ Output Block 72 +✓ Output Block 73 +✓ Output Block 74 +✓ Output Block 75 +✓ Output Block 76 +✓ Output Block 77 +✓ Output Block 78 +✓ Output Block 79 +✓ Output Block 80 +✓ Output Block 81 +✓ Output Block 82 +✗ Output Block 83 +✓ Output Block 84 +✓ Output Block 85 +✗ Output Block 86 +✓ Output Block 87 +✗ Output Block 88 +✓ Output Block 89 +✓ Output Block 90 +✓ Output Block 91 +✓ Output Block 92 +✗ Output Block 93 +✗ Output Block 94 +✓ Output Block 95 +✓ Output Block 96 +✓ Output Block 97 +✓ Output Block 98 +✗ Output Block 99 +✓ Output Block 100 +✓ Output Block 101 +✓ Output Block 102 +✓ Output Block 103 +✗ Output Block 104 +✓ Output Block 105 +✓ Output Block 106 +✗ Output Block 107 +✓ Output Block 108 +✓ Output Block 109 +✗ Output Block 110 +✓ Output Block 111 +✗ Output Block 112 +✓ Output Block 113 +✗ Output Block 114 +✓ Output Block 115 +✗ Output Block 116 +✓ Output Block 117 +✓ Output Block 118 +✓ Output Block 119 +✓ Output Block 120 +✓ Output Block 121 +✓ Output Block 122 +✗ Output Block 123 +✓ Output Block 124 +✓ Output Block 125 +✓ Output Block 126 +✓ Output Block 127 +✓ Output Block 128 +✓ Output Block 129 +✓ Output Block 130 +✗ Output Block 131 +✗ Output Block 132 +✓ Output Block 133 +✓ Output Block 134 +✓ Output Block 135 +✓ Output Block 136 +✓ Output Block 137 +✓ Output Block 138 +✓ Output Block 139 +✓ Output Block 140 +✗ Output Block 141 +✗ Output Block 142 +✗ Output Block 143 +✗ Output Block 144 +✓ Output Block 145 +✓ Output Block 146 +✓ Output Block 147 +✓ Output Block 148 +✓ Output Block 149 +✓ Output Block 150 +✓ Output Block 151 +✗ Output Block 152 +✗ Output Block 153 +✗ nox > Running session unit_tests-3.13 +✗ Error Output + +--- Failed Tests --- +✗ Output Block 11 + 1 feature passed, 0 failed, 0 skipped + 10 scenarios passed, 0 failed, 0 skipped + 23 steps passed, 0 failed, 0 skipped + Took 0min 0.006s + Feature: Devcontainer Lifecycle State Model + As a CleverAgents developer + I want the container lifecycle state model to enforce valid transitions + So that containers follow a predictable state machine + Scenario: ContainerLifecycleState has all required values + When I inspect ContainerLifecycleState enum values + Then the lifecycle enum should contain "discovered" + And the lifecycle enum should contain "building" + And the lifecycle enum should contain "running" + And the lifecycle enum should contain "stopping" + And the lifecycle enum should contain "stopped" + And the lifecycle enum should contain "failed" + +✗ Output Block 14 + Scenario: Invalid transition is rejected + Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000003" + When I attempt to transition from "discovered" to "running" + Then the transition should raise ValueError + +✗ Output Block 15 + Scenario: Stopping to error transition is valid + Given a lifecycle tracker in "stopping" state for resource "01TESTLIFECYCLE0000000004" + When I transition from "stopping" to "failed" + Then the transition should succeed + And the tracker state should be "failed" + +✗ Output Block 16 + Scenario: Error to starting transition is valid for retry + Given a lifecycle tracker in "failed" state for resource "01TESTLIFECYCLE0000000005" + When I transition from "failed" to "building" + Then the transition should succeed + And the tracker state should be "building" + +✗ Output Block 23 + Scenario: validate_transition rejects wrong types + When I call validate_transition with non-enum arguments + Then it should raise TypeError + +✗ Output Block 24 + Scenario: validate_transition rejects wrong to_state type + When I call validate_transition with non-enum to_state + Then it should raise TypeError + +✗ Output Block 25 + Scenario: transition_state rejects wrong tracker type + When I call transition_state with non-tracker argument + Then it should raise TypeError + +✗ Output Block 26 + Scenario: transition_state rejects wrong to_state type + When I call transition_state with non-enum to_state argument + Then it should raise TypeError + Feature: agents resource list shows devcontainer lifecycle state column + As a CleverAgents user + I want the resource list to show the lifecycle state for devcontainer-instance resources + So that I can see whether a devcontainer is discovered, running, stopped, or failed + Feature: agents resource list shows devcontainer lifecycle state column + +✗ Output Block 28 + Scenario: get_lifecycle_tracker rejects empty resource_id + When I attempt to get tracker with empty resource_id + Then the get tracker should raise ValueError + +✗ Output Block 29 + Scenario: set_lifecycle_tracker rejects wrong type + When I attempt to set tracker with wrong type + Then the set tracker should raise TypeError + +✗ Output Block 30 + Scenario: Invalid transition running to building is rejected + Given a lifecycle tracker in "running" state for resource "01TESTLIFECYCLE0000000240" + When I attempt to transition from "running" to "building" + Then the transition should raise ValueError + +✗ Output Block 31 + Scenario: Invalid transition stopped to running is rejected + Given a lifecycle tracker in "stopped" state for resource "01TESTLIFECYCLE0000000241" + When I attempt to transition from "stopped" to "running" + Then the transition should raise ValueError + +✗ Output Block 32 + Scenario: Invalid transition building to stopped is rejected + Given a lifecycle tracker in "building" state for resource "01TESTLIFECYCLE0000000242" + When I attempt to transition from "building" to "stopped" + Then the transition should raise ValueError + +✗ Output Block 33 + Scenario: Invalid transition failed to running is rejected + Given a lifecycle tracker in "failed" state for resource "01TESTLIFECYCLE0000000243" + When I attempt to transition from "failed" to "running" + Then the transition should raise ValueError + +✗ Output Block 34 + Scenario: Invalid transition discovered to stopping is rejected + Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000244" + When I attempt to transition from "discovered" to "stopping" + Then the transition should raise ValueError + +✗ Output Block 35 + Scenario: Invalid transition building to discovered is rejected + Given a lifecycle tracker in "building" state for resource "01TESTLIFECYCLE0000000245" + When I attempt to transition from "building" to "discovered" + Then the transition should raise ValueError + +✗ Output Block 45 + 1 feature passed, 0 failed, 0 skipped + 32 scenarios passed, 0 failed, 0 skipped + 106 steps passed, 0 failed, 0 skipped + Took 0min 0.037s + And built-in types are bootstrapped + Given a devcontainer-instance resource is registered + When I run resource list with all flag + Then the resource output should contain "discovered (not built)" + +✗ Output Block 48 + Scenario: resource list shows "failed" state for a failed devcontainer + Given a fresh in-memory resource registry + And built-in types are bootstrapped + Given a devcontainer-instance resource is registered + And the devcontainer lifecycle state is "failed" + When I run resource list with all flag + Then the resource output should contain "failed" + +✗ FAIL: Output Block 51 + ASSERT FAILED: Expected 'Devcontainer detected' in output, got: + Resources (1 total) + ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ + ┃ ID ┃ Name ┃ Type ┃ Status ┃ Kind ┃ Location ┃ Description ┃ + ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ + │ 01KP2TDV4GAR... │ (unnamed) │ devcontainer-instance │ discovered (not built) │ physical │ /tmp/test-project │ Test devcontainer │ + └─────────────────┴───────────┴───────────────────────┴────────────────────────┴──────────┴───────────────────┴───────────────────┘ + +✗ Output Block 53 + @tdd_issue @tdd_issue_4263 @tdd_expected_fail + Scenario: resource list JSON output includes lifecycle_state for devcontainer-instance + Given a fresh in-memory resource registry + And built-in types are bootstrapped + Given a devcontainer-instance resource is registered + When I run resource list with all flag and format "json" + Then the resource output should be valid JSON + And the resource JSON output should contain key "lifecycle_state" + +✗ FAIL: Output Block 54 + ASSERT FAILED: Key 'lifecycle_state' not found in JSON output + +✗ Output Block 57 + Scenario: resource list succeeds gracefully when lifecycle tracker raises an error + Given a fresh in-memory resource registry + And built-in types are bootstrapped + Feature: Devcontainer Resource Handler + As a CleverAgents developer + I want devcontainer resources to be auto-discovered and manually registered + So that devcontainer environments integrate into the resource model Given a devcontainer-instance resource is registered + +✗ Output Block 58 + Scenario: Register devcontainer-instance manually + Given a temporary directory with a valid devcontainer.json + When I create a devcontainer-instance resource at that path + Then And the resource should have type "the lifecycle tracker raises an error for the resourcedevcontainer-instance" + +✗ Output Block 61 + + Failing scenarios: + features/resource_list_lifecycle_state.feature:44 resource list shows warning banner when devcontainer is in detected state + +✗ Output Block 62 + 0 features passed, 1 failed, 0 skipped + 11 scenarios passed, 1 failed, 0 skipped + 69 steps passed, 1 failed, 0 skipped + Took 0min 1.827s + +✗ Output Block 83 + Scenario Outline: validate_clone_into_url rejects invalid URLs -- @1.1 + When I validate clone-into URL "not-a-url" + Then it should raise a ValueError + Then the clone-into URL should be invalid + +✗ Output Block 86 + Scenario: clone_repo_into_container rejects empty container_id + When I call clone_repo_into_container with empty container_id + Then it should raise ValueError mentioning "container_id" + +✗ Output Block 88 + Scenario: clone_repo_into_container rejects empty repo_url + When I call clone_repo_into_container with empty repo_url + And the directory has a named devcontainer configuration "frontend" + Then it should raise ValueError mentioning "repo_url" + When I run devcontainer discovery on the directory as "git-checkout" + Then discovery should return 2 results + And the result config names should include "api" + +✗ Output Block 93 + Scenario: clone_repo_into_container raises CloneIntoError when docker exec fails + Given a mock docker exec that fails for clone with stderr "fatal: repository not found" + When I clone "https://github.com/org/missing.git" into container "abc123def456" + When I run devcontainer discovery on the directory as "git-checkout" + Then the clone should raise CloneIntoError + And the CloneIntoError should mention "repository not found" + Then discovery should return 1 result + And the first result should have no config name + +✗ Output Block 94 + Scenario: CloneIntoError stores repo_url, container_id, and stderr + Given a CloneIntoError with repo "https://example.com/repo.git" container "ctr-001" stderr "auth failed" + Then the CloneIntoError repo_url should be "https://example.com/repo.git" + And the CloneIntoError container_id should be "ctr-001" + And the CloneIntoError stderr should be "auth failed" + +✗ Output Block 99 + 1 feature passed, 0 failed, 0 skipped + 17 scenarios passed, 0 failed, 0 skipped + 44 steps passed, 0 failed, 0 skipped + Took 0min 0.008s + Then discovery should return 0 results + +✗ Output Block 104 + 1 feature passed, 0 failed, 0 skipped + 26 scenarios passed, 0 failed, 0 skipped + 90 steps passed, 0 failed, 0 skipped + Took 0min 0.026s + Feature: Devcontainer Session Cleanup and CLI Commands + As a CleverAgents developer + I want session-scoped container cleanup and CLI stop/rebuild commands + So that containers are cleaned up when sessions end and can be managed via CLI + Scenario: Active containers stopped on session cleanup + Given a mock devcontainer CLI runner + And an active container "01TESTLIFECYCLE0000000040" with container ID "ctr-a" + And an active container "01TESTLIFECYCLE0000000041" with container ID "ctr-b" + When I run session cleanup for session "session-001" + Then the container state for "01TESTLIFECYCLE0000000040" should be "stopped" + And the container state for "01TESTLIFECYCLE0000000041" should be "stopped" + +✗ Output Block 107 + Scenario: Session cleanup handles stop failure gracefully + Given a mock devcontainer CLI runner + And the runner configured to raise exception on stop + And an active container "01TESTLIFECYCLE0000000130" with container ID "ctr-fail" + When I run session cleanup for session "session-err" + Then the cleanup should return 0 stopped containers + +✗ Output Block 110 + Scenario: dcproto delete fails when container is stopped + Given dcproto a devcontainer handler + And dcproto a devcontainer-instance resource with location "/ws/project" + And dcproto subprocess returns failed rm output with stderr "container not running" + When dcproto I delete path "file.txt" from the resource + Then the CLI exit code should be 0 + And the CLI output should contain "Stopped" + Then dcproto the delete should fail + And dcproto the delete failure message should contain "container not running" + +✗ Output Block 112 + Scenario: dcproto delete from resource with no location raises ValueError + Given dcproto a devcontainer handler + And dcproto a devcontainer-instance resource with no location + When dcproto I delete path "any/file.txt" from the resource + Then dcproto the delete should raise ValueError containing "no location" + +✗ Output Block 114 + Scenario: dcproto list_children returns empty list when container is stopped + Given dcproto a devcontainer handler + And dcproto a devcontainer-instance resource with location "/ws/project" + And dcproto subprocess returns failed ls output + When dcproto I list children of the resource + Then dcproto the children list should have 0 entries + +✗ Output Block 116 + Scenario: dcproto list_children from resource with no location raises ValueError + Given dcproto a devcontainer handler + And dcproto a devcontainer-instance resource with no location + When dcproto I list children of the resource + Then dcproto the list children should raise ValueError containing "no location" + +✗ Output Block 123 + Scenario: dcproto diff from resource with no location raises ValueError + Given dcproto a devcontainer handler + And dcproto a devcontainer-instance resource with no location + When dcproto I diff the resource against "/some/path" + Then the devcontainer CLI exit code should be non-zero + Then dcproto the diff should raise ValueError containing "no location" + And the CLI output should contain "no location" + +✗ Output Block 131 + Scenario: dcproto create_sandbox raises ValueError for resource with no location + Given dcproto a devcontainer handler + And dcproto a devcontainer-instance resource with no location + And dcproto a mock sandbox manager that returns a valid sandbox + When dcproto I create a sandbox for the resource with plan "plan-004" + Then dcproto the create_sandbox should raise ValueError containing "no location" + +✗ Output Block 132 + 1 feature passed, 0 failed, 0 skipped + 18 scenarios passed, 0 failed, 0 skipped + 102 steps passed, 0 failed, 0 skipped + Took 0min 0.028s + Then the CLI exit code should be 0 + And the CLI rebuild mock should have been called with "01TESTLIFECYCLE0000000221" and "/workspace/project" + +✗ Output Block 141 + Scenario: CLI stop handles NotFoundError from show_resource + Given a mock resource service that raises NotFoundError for "local/missing" + When I invoke CLI resource stop "local/missing" + Then the devcontainer CLI exit code should be non-zero + And the CLI output should contain "Resource not found" + +✗ Output Block 142 + Scenario: CLI rebuild handles NotFoundError from show_resource + Given a mock resource service that raises NotFoundError for "local/missing-rb" + When I invoke CLI resource rebuild "local/missing-rb" + Then the devcontainer CLI exit code should be non-zero + And the CLI output should contain "Resource not found" + +✗ Output Block 143 + Scenario: CLI stop handles RuntimeError from stop_container + Given a mock resource service with devcontainer "local/dc-rterr" id "01TESTLIFECYCLE0000000500" at "/workspace/project" + And an active container "01TESTLIFECYCLE0000000500" with container ID "ctr-rterr" + And CLI stop_container mock that raises RuntimeError + When I invoke CLI resource stop with error mock "local/dc-rterr" + Then the devcontainer CLI exit code should be non-zero + And the CLI output should contain "Stop failed" + +✗ Output Block 144 + Scenario: CLI rebuild handles RuntimeError from rebuild_container + Given a mock resource service with devcontainer "local/dc-rberr" id "01TESTLIFECYCLE0000000501" at "/workspace/project" + And a stopped container "01TESTLIFECYCLE0000000501" + And CLI rebuild_container mock that raises RuntimeError + When I invoke CLI resource rebuild with error mock "local/dc-rberr" + Then the devcontainer CLI exit code should be non-zero + And the CLI output should contain "Rebuild failed" + +✗ Output Block 152 + 1 feature passed, 0 failed, 0 skipped + 30 scenarios passed, 0 failed, 0 skipped + 143 steps passed, 0 failed, 0 skipped + Took 0min 0.436s + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code: + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12 + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code: + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12 + 2026-04-13 07:00:42 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code: + 2026-04-13 07:00:42 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY + 2026-04-13 07:00:42 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED + 2026-04-13 07:00:42 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12 + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code: + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12 + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code: + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12 + Including unscoped container 01TESTLIFECYCLE0000000040 in session 'session-001' cleanup (container has no session_id set) + Including unscoped container 01TESTLIFECYCLE0000000041 in session 'session-001' cleanup (container has no session_id set) + Container 01TESTLIFECYCLE0000000040: running -> stopping (manual stop) + Container 01TESTLIFECYCLE0000000040: stopping -> stopped (container stopped) + Stopped container 01TESTLIFECYCLE0000000040 (session=session-001) + Container 01TESTLIFECYCLE0000000041: running -> stopping (manual stop) + Container 01TESTLIFECYCLE0000000041: stopping -> stopped (container stopped) + Stopped container 01TESTLIFECYCLE0000000041 (session=session-001) + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code: + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12 + Registered built-in resource type: fs-mount + Registered built-in resource type: git-checkout + Registered built-in resource type: fs-directory + Registered built-in resource type: container-instance + Registered built-in resource type: devcontainer-instance + Registered built-in resource type: devcontainer-file + Registered built-in resource type: git + Registered built-in resource type: git-remote + Registered built-in resource type: git-branch + Registered built-in resource type: git-tag + Registered built-in resource type: git-commit + Registered built-in resource type: git-tree + Registered built-in resource type: git-tree-entry + Registered built-in resource type: git-stash + Registered built-in resource type: git-submodule + Registered built-in resource type: fs-symlink + Registered built-in resource type: fs-hardlink + Registered built-in resource type: cloud-account + Registered built-in resource type: cloud-region + Registered built-in resource type: cloud-network + Registered built-in resource type: cloud-subnet + Registered built-in resource type: cloud-security-group + Registered built-in resource type: cloud-load-balancer + Registered built-in resource type: cloud-compute-instance + Registered built-in resource type: cloud-object-store + Registered built-in resource type: cloud-block-storage + Registered built-in resource type: cloud-identity-principal + Registered built-in resource type: cloud-role + Registered built-in resource type: cloud-policy + Registered built-in resource type: cloud-log-group + Registered built-in resource type: cloud-alarm + Registered built-in resource type: cloud-queue + Registered built-in resource type: cloud-topic + Registered built-in resource type: cloud-container-repo + Registered built-in resource type: cloud-container-cluster + Registered built-in resource type: cloud-container-service + Registered built-in resource type: aws-account + Registered built-in resource type: aws-region + Registered built-in resource type: aws-vpc + Registered built-in resource type: aws-subnet + Registered built-in resource type: aws-igw + Registered built-in resource type: aws-nat-gw + Registered built-in resource type: aws-route-table + Registered built-in resource type: aws-nacl + Registered built-in resource type: aws-security-group + Registered built-in resource type: aws-alb + Registered built-in resource type: aws-nlb + Registered built-in resource type: aws-target-group + Registered built-in resource type: aws-listener + Registered built-in resource type: aws-ec2-instance + Registered built-in resource type: aws-ami + Registered built-in resource type: aws-launch-template + Registered built-in resource type: aws-asg + Registered built-in resource type: aws-s3-bucket + Registered built-in resource type: aws-ebs-volume + Registered built-in resource type: aws-efs-filesystem + Registered built-in resource type: aws-iam-user + Registered built-in resource type: aws-iam-role + Registered built-in resource type: aws-iam-policy + Registered built-in resource type: aws-iam-instance-profile + Registered built-in resource type: aws-cloudwatch-log-group + Registered built-in resource type: aws-cloudwatch-alarm + Registered built-in resource type: aws-cloudwatch-metric + Registered built-in resource type: aws-eventbridge-bus + Registered built-in resource type: aws-eventbridge-rule + Registered built-in resource type: aws-eventbridge-target + Registered built-in resource type: aws-sqs-queue + Registered built-in resource type: aws-sns-topic + Registered built-in resource type: aws-sns-subscription + Registered built-in resource type: aws-ecr-repo + Registered built-in resource type: aws-ecs-cluster + Registered built-in resource type: aws-ecs-service + Registered built-in resource type: aws-ecs-task-def + Registered built-in resource type: aws-eks-cluster + Registered built-in resource type: aws-eks-nodegroup + Registered built-in resource type: gcp + Registered built-in resource type: azure + Registered built-in resource type: container-runtime + Registered built-in resource type: container-image + Registered built-in resource type: container-mount + Registered built-in resource type: container-exec-env + Registered built-in resource type: container-port + Registered built-in resource type: container-volume + Registered built-in resource type: container-network + Registered built-in resource type: postgres + Registered built-in resource type: mysql + Registered built-in resource type: sqlite + Registered built-in resource type: duckdb + Registered built-in resource type: file + Registered built-in resource type: directory + Registered built-in resource type: commit + Registered built-in resource type: branch + Registered built-in resource type: tag + Registered built-in resource type: tree + Registered built-in resource type: remote + Registered built-in resource type: submodule + Registered built-in resource type: symlink + Registered built-in resource type: executable + Registered built-in resource type: lsp-server + Registered built-in resource type: lsp-workspace + Registered built-in resource type: lsp-document + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code: + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED + 2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12 + +✗ Output Block 153 + Overall summary: + 6 features passed, 1 failed, 0 errored, 0 skipped + 144 scenarios passed, 1 failed, 0 errored, 0 skipped + 553 steps passed, 1 failed, 0 errored, 0 skipped + Took 2.366s + Wall time: 2m 47.786s + +✗ nox > Running session unit_tests-3.13 + nox > Running session unit_tests-3.13 + nox > Reusing existing virtual environment at .nox/unit_tests-3-13. + nox > uv pip install -e '.[tests]' + nox > uv pip install setuptools wheel + nox > uv pip install .nox/unit_tests-3-13/tmp/behave-parallel-inprocess + nox > python scripts/create_template_db.py /tmp/impl-worker-7555-20260413/build/.template-migrated.db + nox > python -m compileall -q features/ + nox > /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature + nox > Command /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature failed with exit code 1 + nox > Session unit_tests-3.13 failed. + +✗ Error Output + nox > Running session unit_tests-3.13 + nox > Reusing existing virtual environment at .nox/unit_tests-3-13. + nox > uv pip install -e '.[tests]' + nox > uv pip install setuptools wheel + nox > uv pip install .nox/unit_tests-3-13/tmp/behave-parallel-inprocess + nox > python scripts/create_template_db.py /tmp/impl-worker-7555-20260413/build/.template-migrated.db + nox > python -m compileall -q features/ + nox > /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature + nox > Command /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature failed with exit code 1 + nox > Session unit_tests-3.13 failed. + + diff --git a/test_reports/test_results.json b/test_reports/test_results.json new file mode 100644 index 000000000..acafaa4f7 --- /dev/null +++ b/test_reports/test_results.json @@ -0,0 +1,2054 @@ +{ + "framework": "generic", + "tests": [ + { + "name": "Output Block 1", + "passed": true, + "output": [ + "@devcontainer-sandbox", + "Feature: devcontainer-instance uses snapshot sandbox strategy", + " As a CleverAgents developer", + " I want devcontainer-instance resources to use the snapshot sandbox strategy", + " So that plan execution inside containers is safely isolated", + " Scenario: DevcontainerHandler default strategy is snapshot ", + " When I inspect DevcontainerHandler class attributes", + " Then the DevcontainerHandler _default_strategy should be \"snapshot\"" + ], + "rawOutput": "@devcontainer-sandbox\nFeature: devcontainer-instance uses snapshot sandbox strategy\n As a CleverAgents developer\n I want devcontainer-instance resources to use the snapshot sandbox strategy\n So that plan execution inside containers is safely isolated\n Scenario: DevcontainerHandler default strategy is snapshot \n When I inspect DevcontainerHandler class attributes\n Then the DevcontainerHandler _default_strategy should be \"snapshot\"" + }, + { + "name": "Output Block 2", + "passed": true, + "output": [ + " Scenario: devcontainer-instance resource type has snapshot sandbox strategy ", + " When I look up the \"devcontainer-instance\" resource type spec", + " Then the spec sandbox_strategy should be \"snapshot\"" + ], + "rawOutput": " Scenario: devcontainer-instance resource type has snapshot sandbox strategy \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec sandbox_strategy should be \"snapshot\"" + }, + { + "name": "Output Block 3", + "passed": true, + "output": [ + " Scenario: devcontainer-instance child_types includes container-mount ", + " When I look up the \"devcontainer-instance\" resource type spec", + " Then the spec child_types should contain \"container-mount\"" + ], + "rawOutput": " Scenario: devcontainer-instance child_types includes container-mount \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec child_types should contain \"container-mount\"" + }, + { + "name": "Output Block 4", + "passed": true, + "output": [ + " Scenario: devcontainer-instance child_types includes container-exec-env ", + " When I look up the \"devcontainer-instance\" resource type spec", + " Then the spec child_types should contain \"container-exec-env\"" + ], + "rawOutput": " Scenario: devcontainer-instance child_types includes container-exec-env \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec child_types should contain \"container-exec-env\"" + }, + { + "name": "Output Block 5", + "passed": true, + "output": [ + " Scenario: devcontainer-instance child_types includes container-port ", + " When I look up the \"devcontainer-instance\" resource type spec", + " Then the spec child_types should contain \"container-port\"" + ], + "rawOutput": " Scenario: devcontainer-instance child_types includes container-port \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec child_types should contain \"container-port\"" + }, + { + "name": "Output Block 6", + "passed": true, + "output": [ + " Scenario: devcontainer-instance child_types includes devcontainer-file ", + " When I look up the \"devcontainer-instance\" resource type spec", + " Then the spec child_types should contain \"devcontainer-file\"" + ], + "rawOutput": " Scenario: devcontainer-instance child_types includes devcontainer-file \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec child_types should contain \"devcontainer-file\"" + }, + { + "name": "Output Block 7", + "passed": true, + "output": [ + " Scenario: ContainerLifecycleTracker default state is discovered ", + " When I create a new ContainerLifecycleTracker for resource \"01SANDBOX0000000000000001\"", + " Then the tracker initial state should be \"discovered\"" + ], + "rawOutput": " Scenario: ContainerLifecycleTracker default state is discovered \n When I create a new ContainerLifecycleTracker for resource \"01SANDBOX0000000000000001\"\n Then the tracker initial state should be \"discovered\"" + }, + { + "name": "Output Block 8", + "passed": true, + "output": [ + " Scenario: ContainerLifecycleState has discovered value ", + " When I inspect ContainerLifecycleState enum values", + " Then the lifecycle enum values should contain \"discovered\"", + " And the lifecycle enum values should not contain \"detected\"" + ], + "rawOutput": " Scenario: ContainerLifecycleState has discovered value \n When I inspect ContainerLifecycleState enum values\n Then the lifecycle enum values should contain \"discovered\"\n And the lifecycle enum values should not contain \"detected\"" + }, + { + "name": "Output Block 9", + "passed": true, + "output": [ + " Scenario: devcontainer-instance in BUILTIN_TYPES has snapshot strategy ", + " When I look up \"devcontainer-instance\" in BUILTIN_TYPES", + " Then the BUILTIN_TYPES entry sandbox_strategy should be \"snapshot\"" + ], + "rawOutput": " Scenario: devcontainer-instance in BUILTIN_TYPES has snapshot strategy \n When I look up \"devcontainer-instance\" in BUILTIN_TYPES\n Then the BUILTIN_TYPES entry sandbox_strategy should be \"snapshot\"" + }, + { + "name": "Output Block 10", + "passed": true, + "output": [ + " Scenario: devcontainer-instance in BUILTIN_TYPES has correct child_types ", + " When I look up \"devcontainer-instance\" in BUILTIN_TYPES", + " Then the BUILTIN_TYPES entry child_types should include \"container-mount\"", + " And the BUILTIN_TYPES entry child_types should include \"container-exec-env\"", + " And the BUILTIN_TYPES entry child_types should include \"container-port\"" + ], + "rawOutput": " Scenario: devcontainer-instance in BUILTIN_TYPES has correct child_types \n When I look up \"devcontainer-instance\" in BUILTIN_TYPES\n Then the BUILTIN_TYPES entry child_types should include \"container-mount\"\n And the BUILTIN_TYPES entry child_types should include \"container-exec-env\"\n And the BUILTIN_TYPES entry child_types should include \"container-port\"" + }, + { + "name": "Output Block 11", + "passed": false, + "output": [ + "1 feature passed, 0 failed, 0 skipped", + "10 scenarios passed, 0 failed, 0 skipped", + "23 steps passed, 0 failed, 0 skipped", + "Took 0min 0.006s", + "Feature: Devcontainer Lifecycle State Model", + " As a CleverAgents developer", + " I want the container lifecycle state model to enforce valid transitions", + " So that containers follow a predictable state machine", + " Scenario: ContainerLifecycleState has all required values ", + " When I inspect ContainerLifecycleState enum values", + " Then the lifecycle enum should contain \"discovered\"", + " And the lifecycle enum should contain \"building\"", + " And the lifecycle enum should contain \"running\"", + " And the lifecycle enum should contain \"stopping\"", + " And the lifecycle enum should contain \"stopped\"", + " And the lifecycle enum should contain \"failed\"" + ], + "rawOutput": "1 feature passed, 0 failed, 0 skipped\n10 scenarios passed, 0 failed, 0 skipped\n23 steps passed, 0 failed, 0 skipped\nTook 0min 0.006s\nFeature: Devcontainer Lifecycle State Model\n As a CleverAgents developer\n I want the container lifecycle state model to enforce valid transitions\n So that containers follow a predictable state machine\n Scenario: ContainerLifecycleState has all required values \n When I inspect ContainerLifecycleState enum values\n Then the lifecycle enum should contain \"discovered\"\n And the lifecycle enum should contain \"building\"\n And the lifecycle enum should contain \"running\"\n And the lifecycle enum should contain \"stopping\"\n And the lifecycle enum should contain \"stopped\"\n And the lifecycle enum should contain \"failed\"" + }, + { + "name": "Output Block 12", + "passed": true, + "output": [ + " Scenario: Valid transitions are accepted ", + " Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000001\"", + " When I transition from \"discovered\" to \"building\"", + " Then the transition should succeed", + " And the tracker state should be \"building\"" + ], + "rawOutput": " Scenario: Valid transitions are accepted \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000001\"\n When I transition from \"discovered\" to \"building\"\n Then the transition should succeed\n And the tracker state should be \"building\"" + }, + { + "name": "Output Block 13", + "passed": true, + "output": [ + " Scenario: Active transition after starting ", + " Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000002\"", + " When I transition from \"building\" to \"running\"", + " Then the transition should succeed", + " And the tracker state should be \"running\"" + ], + "rawOutput": " Scenario: Active transition after starting \n Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000002\"\n When I transition from \"building\" to \"running\"\n Then the transition should succeed\n And the tracker state should be \"running\"" + }, + { + "name": "Output Block 14", + "passed": false, + "output": [ + " Scenario: Invalid transition is rejected ", + " Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000003\"", + " When I attempt to transition from \"discovered\" to \"running\"", + " Then the transition should raise ValueError" + ], + "rawOutput": " Scenario: Invalid transition is rejected \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000003\"\n When I attempt to transition from \"discovered\" to \"running\"\n Then the transition should raise ValueError" + }, + { + "name": "Output Block 15", + "passed": false, + "output": [ + " Scenario: Stopping to error transition is valid ", + " Given a lifecycle tracker in \"stopping\" state for resource \"01TESTLIFECYCLE0000000004\"", + " When I transition from \"stopping\" to \"failed\"", + " Then the transition should succeed", + " And the tracker state should be \"failed\"" + ], + "rawOutput": " Scenario: Stopping to error transition is valid \n Given a lifecycle tracker in \"stopping\" state for resource \"01TESTLIFECYCLE0000000004\"\n When I transition from \"stopping\" to \"failed\"\n Then the transition should succeed\n And the tracker state should be \"failed\"" + }, + { + "name": "Output Block 16", + "passed": false, + "output": [ + " Scenario: Error to starting transition is valid for retry ", + " Given a lifecycle tracker in \"failed\" state for resource \"01TESTLIFECYCLE0000000005\"", + " When I transition from \"failed\" to \"building\"", + " Then the transition should succeed", + " And the tracker state should be \"building\"" + ], + "rawOutput": " Scenario: Error to starting transition is valid for retry \n Given a lifecycle tracker in \"failed\" state for resource \"01TESTLIFECYCLE0000000005\"\n When I transition from \"failed\" to \"building\"\n Then the transition should succeed\n And the tracker state should be \"building\"" + }, + { + "name": "Output Block 17", + "passed": true, + "output": [ + " Scenario: Stopped to starting transition is valid for rebuild ", + " Given a lifecycle tracker in \"stopped\" state for resource \"01TESTLIFECYCLE0000000006\"", + " When I transition from \"stopped\" to \"building\"", + " Then the transition should succeed" + ], + "rawOutput": " Scenario: Stopped to starting transition is valid for rebuild \n Given a lifecycle tracker in \"stopped\" state for resource \"01TESTLIFECYCLE0000000006\"\n When I transition from \"stopped\" to \"building\"\n Then the transition should succeed" + }, + { + "name": "Output Block 18", + "passed": true, + "output": [ + " Scenario: Transition history is recorded ", + " Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000007\"", + " When I transition from \"discovered\" to \"building\"", + " Then the tracker should have 1 transition in history" + ], + "rawOutput": " Scenario: Transition history is recorded \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000007\"\n When I transition from \"discovered\" to \"building\"\n Then the tracker should have 1 transition in history" + }, + { + "name": "Output Block 19", + "passed": true, + "output": [ + " Scenario: Lifecycle tracker persists state across calls ", + " Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000060\"", + " When I transition from \"discovered\" to \"building\"", + " And I retrieve the tracker for \"01TESTLIFECYCLE0000000060\"", + " Then the retrieved tracker state should be \"building\"" + ], + "rawOutput": " Scenario: Lifecycle tracker persists state across calls \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000060\"\n When I transition from \"discovered\" to \"building\"\n And I retrieve the tracker for \"01TESTLIFECYCLE0000000060\"\n Then the retrieved tracker state should be \"building\"" + }, + { + "name": "Output Block 20", + "passed": true, + "output": [ + " Scenario: List active containers returns only active ones ", + " Given a mock devcontainer CLI runner", + " And the runner configured for successful activation", + " And an active container \"01TESTLIFECYCLE0000000070\" with container ID \"ctr-x\"", + " And a lifecycle tracker for resource \"01TESTLIFECYCLE0000000071\"", + " When I list active containers", + " Then the active list should contain \"01TESTLIFECYCLE0000000070\"", + " And the active list should not contain \"01TESTLIFECYCLE0000000071\"" + ], + "rawOutput": " Scenario: List active containers returns only active ones \n Given a mock devcontainer CLI runner\n And the runner configured for successful activation\n And an active container \"01TESTLIFECYCLE0000000070\" with container ID \"ctr-x\"\n And a lifecycle tracker for resource \"01TESTLIFECYCLE0000000071\"\n When I list active containers\n Then the active list should contain \"01TESTLIFECYCLE0000000070\"\n And the active list should not contain \"01TESTLIFECYCLE0000000071\"" + }, + { + "name": "Output Block 21", + "passed": true, + "output": [ + " Scenario: Parse valid devcontainer up JSON output ", + " When I parse devcontainer up output with containerId \"abcdef123456\" and workspace \"/ws\"", + " Then the parsed container ID should be \"abcdef123456\"", + " And the parsed workspace path should be \"/ws\"" + ], + "rawOutput": " Scenario: Parse valid devcontainer up JSON output \n When I parse devcontainer up output with containerId \"abcdef123456\" and workspace \"/ws\"\n Then the parsed container ID should be \"abcdef123456\"\n And the parsed workspace path should be \"/ws\"" + }, + { + "name": "Output Block 22", + "passed": true, + "output": [ + " Scenario: Parse invalid devcontainer up JSON output gracefully ", + " When I parse devcontainer up output with invalid JSON", + " Then the parsed container ID should be None", + " And the parsed workspace path should be None" + ], + "rawOutput": " Scenario: Parse invalid devcontainer up JSON output gracefully \n When I parse devcontainer up output with invalid JSON\n Then the parsed container ID should be None\n And the parsed workspace path should be None" + }, + { + "name": "Output Block 23", + "passed": false, + "output": [ + " Scenario: validate_transition rejects wrong types ", + " When I call validate_transition with non-enum arguments", + " Then it should raise TypeError" + ], + "rawOutput": " Scenario: validate_transition rejects wrong types \n When I call validate_transition with non-enum arguments\n Then it should raise TypeError" + }, + { + "name": "Output Block 24", + "passed": false, + "output": [ + " Scenario: validate_transition rejects wrong to_state type ", + " When I call validate_transition with non-enum to_state", + " Then it should raise TypeError" + ], + "rawOutput": " Scenario: validate_transition rejects wrong to_state type \n When I call validate_transition with non-enum to_state\n Then it should raise TypeError" + }, + { + "name": "Output Block 25", + "passed": false, + "output": [ + " Scenario: transition_state rejects wrong tracker type ", + " When I call transition_state with non-tracker argument", + " Then it should raise TypeError" + ], + "rawOutput": " Scenario: transition_state rejects wrong tracker type \n When I call transition_state with non-tracker argument\n Then it should raise TypeError" + }, + { + "name": "Output Block 26", + "passed": false, + "output": [ + " Scenario: transition_state rejects wrong to_state type ", + " When I call transition_state with non-enum to_state argument", + " Then it should raise TypeError", + "Feature: agents resource list shows devcontainer lifecycle state column", + " As a CleverAgents user", + " I want the resource list to show the lifecycle state for devcontainer-instance resources", + " So that I can see whether a devcontainer is discovered, running, stopped, or failed", + " Feature: agents resource list shows devcontainer lifecycle state column " + ], + "rawOutput": " Scenario: transition_state rejects wrong to_state type \n When I call transition_state with non-enum to_state argument\n Then it should raise TypeError\nFeature: agents resource list shows devcontainer lifecycle state column\n As a CleverAgents user\n I want the resource list to show the lifecycle state for devcontainer-instance resources\n So that I can see whether a devcontainer is discovered, running, stopped, or failed\n Feature: agents resource list shows devcontainer lifecycle state column " + }, + { + "name": "Output Block 27", + "passed": true, + "output": [ + " Scenario: resource list shows \"Status\" column header ", + " Given a fresh in-memory resource registry" + ], + "rawOutput": " Scenario: resource list shows \"Status\" column header \n Given a fresh in-memory resource registry" + }, + { + "name": "Output Block 28", + "passed": false, + "output": [ + " Scenario: get_lifecycle_tracker rejects empty resource_id ", + " When I attempt to get tracker with empty resource_id", + " Then the get tracker should raise ValueError" + ], + "rawOutput": " Scenario: get_lifecycle_tracker rejects empty resource_id \n When I attempt to get tracker with empty resource_id\n Then the get tracker should raise ValueError" + }, + { + "name": "Output Block 29", + "passed": false, + "output": [ + " Scenario: set_lifecycle_tracker rejects wrong type ", + " When I attempt to set tracker with wrong type", + " Then the set tracker should raise TypeError" + ], + "rawOutput": " Scenario: set_lifecycle_tracker rejects wrong type \n When I attempt to set tracker with wrong type\n Then the set tracker should raise TypeError" + }, + { + "name": "Output Block 30", + "passed": false, + "output": [ + " Scenario: Invalid transition running to building is rejected ", + " Given a lifecycle tracker in \"running\" state for resource \"01TESTLIFECYCLE0000000240\"", + " When I attempt to transition from \"running\" to \"building\"", + " Then the transition should raise ValueError" + ], + "rawOutput": " Scenario: Invalid transition running to building is rejected \n Given a lifecycle tracker in \"running\" state for resource \"01TESTLIFECYCLE0000000240\"\n When I attempt to transition from \"running\" to \"building\"\n Then the transition should raise ValueError" + }, + { + "name": "Output Block 31", + "passed": false, + "output": [ + " Scenario: Invalid transition stopped to running is rejected ", + " Given a lifecycle tracker in \"stopped\" state for resource \"01TESTLIFECYCLE0000000241\"", + " When I attempt to transition from \"stopped\" to \"running\"", + " Then the transition should raise ValueError" + ], + "rawOutput": " Scenario: Invalid transition stopped to running is rejected \n Given a lifecycle tracker in \"stopped\" state for resource \"01TESTLIFECYCLE0000000241\"\n When I attempt to transition from \"stopped\" to \"running\"\n Then the transition should raise ValueError" + }, + { + "name": "Output Block 32", + "passed": false, + "output": [ + " Scenario: Invalid transition building to stopped is rejected ", + " Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000242\"", + " When I attempt to transition from \"building\" to \"stopped\"", + " Then the transition should raise ValueError" + ], + "rawOutput": " Scenario: Invalid transition building to stopped is rejected \n Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000242\"\n When I attempt to transition from \"building\" to \"stopped\"\n Then the transition should raise ValueError" + }, + { + "name": "Output Block 33", + "passed": false, + "output": [ + " Scenario: Invalid transition failed to running is rejected ", + " Given a lifecycle tracker in \"failed\" state for resource \"01TESTLIFECYCLE0000000243\"", + " When I attempt to transition from \"failed\" to \"running\"", + " Then the transition should raise ValueError" + ], + "rawOutput": " Scenario: Invalid transition failed to running is rejected \n Given a lifecycle tracker in \"failed\" state for resource \"01TESTLIFECYCLE0000000243\"\n When I attempt to transition from \"failed\" to \"running\"\n Then the transition should raise ValueError" + }, + { + "name": "Output Block 34", + "passed": false, + "output": [ + " Scenario: Invalid transition discovered to stopping is rejected ", + " Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000244\"", + " When I attempt to transition from \"discovered\" to \"stopping\"", + " Then the transition should raise ValueError" + ], + "rawOutput": " Scenario: Invalid transition discovered to stopping is rejected \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000244\"\n When I attempt to transition from \"discovered\" to \"stopping\"\n Then the transition should raise ValueError" + }, + { + "name": "Output Block 35", + "passed": false, + "output": [ + " Scenario: Invalid transition building to discovered is rejected ", + " Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000245\"", + " When I attempt to transition from \"building\" to \"discovered\"", + " Then the transition should raise ValueError" + ], + "rawOutput": " Scenario: Invalid transition building to discovered is rejected \n Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000245\"\n When I attempt to transition from \"building\" to \"discovered\"\n Then the transition should raise ValueError" + }, + { + "name": "Output Block 36", + "passed": true, + "output": [ + " Scenario: Parse devcontainer up output with log lines before JSON ", + " When I parse devcontainer up output with log lines before JSON", + " Then the parsed container ID should be \"aabbccddee0099887766\"", + " And the parsed workspace path should be \"/workspaces/multi\"" + ], + "rawOutput": " Scenario: Parse devcontainer up output with log lines before JSON \n When I parse devcontainer up output with log lines before JSON\n Then the parsed container ID should be \"aabbccddee0099887766\"\n And the parsed workspace path should be \"/workspaces/multi\"" + }, + { + "name": "Output Block 37", + "passed": true, + "output": [ + " Scenario: Transition history is capped at 100 entries ", + " Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000300\"", + " When I perform 105 transitions cycling through valid states", + " Then the tracker should have at most 100 transitions" + ], + "rawOutput": " Scenario: Transition history is capped at 100 entries \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000300\"\n When I perform 105 transitions cycling through valid states\n Then the tracker should have at most 100 transitions" + }, + { + "name": "Output Block 38", + "passed": true, + "output": [ + " Scenario: Invalid container ID format is rejected during parsing ", + " When I parse devcontainer up output with invalid container ID \"not-a-hex-id!\"", + " Then the parsed container ID should be empty", + " And built-in types are bootstrapped" + ], + "rawOutput": " Scenario: Invalid container ID format is rejected during parsing \n When I parse devcontainer up output with invalid container ID \"not-a-hex-id!\"\n Then the parsed container ID should be empty\n And built-in types are bootstrapped" + }, + { + "name": "Output Block 39", + "passed": true, + "output": [ + " Scenario: Parse devcontainer up output with relative workspace path ", + " When I parse devcontainer up output with relative workspace \"relative/ws\"", + " Then the parsed container ID should be \"aabbccddee0011223344\"", + " Given a devcontainer-instance resource is registered", + " And the parsed workspace path should be None", + " When I run resource list with all flag" + ], + "rawOutput": " Scenario: Parse devcontainer up output with relative workspace path \n When I parse devcontainer up output with relative workspace \"relative/ws\"\n Then the parsed container ID should be \"aabbccddee0011223344\"\n Given a devcontainer-instance resource is registered\n And the parsed workspace path should be None\n When I run resource list with all flag" + }, + { + "name": "Output Block 40", + "passed": true, + "output": [ + " Scenario: Terminal tracker eviction removes oldest stopped trackers ", + " Given 210 stopped container trackers in the registry", + " When I run terminal tracker eviction", + " Then the registry should have at most 200 terminal trackers" + ], + "rawOutput": " Scenario: Terminal tracker eviction removes oldest stopped trackers \n Given 210 stopped container trackers in the registry\n When I run terminal tracker eviction\n Then the registry should have at most 200 terminal trackers" + }, + { + "name": "Output Block 41", + "passed": true, + "output": [ + " Scenario: evict_terminal_trackers returns 0 when below threshold ", + " Given 50 stopped container trackers in the registry", + " When I run terminal tracker eviction", + " Then the eviction count should be 0" + ], + "rawOutput": " Scenario: evict_terminal_trackers returns 0 when below threshold \n Given 50 stopped container trackers in the registry\n When I run terminal tracker eviction\n Then the eviction count should be 0" + }, + { + "name": "Output Block 42", + "passed": true, + "output": [ + " Scenario: get_lifecycle_tracker auto-creates tracker for new resource ", + " When I get tracker for new resource \"01TESTLIFECYCLE0000000730\"", + " Then the auto-created tracker state should be \"discovered\"", + " And the auto-created tracker resource_id should be \"01TESTLIFECYCLE0000000730\"", + " Then the resource output should contain \"Status\"" + ], + "rawOutput": " Scenario: get_lifecycle_tracker auto-creates tracker for new resource \n When I get tracker for new resource \"01TESTLIFECYCLE0000000730\"\n Then the auto-created tracker state should be \"discovered\"\n And the auto-created tracker resource_id should be \"01TESTLIFECYCLE0000000730\"\n Then the resource output should contain \"Status\"" + }, + { + "name": "Output Block 43", + "passed": true, + "output": [ + " Scenario: clear_lifecycle_registry stops health check threads ", + " Given a mock devcontainer CLI runner", + " And the runner configured for successful health check", + " And an active container \"01TESTLIFECYCLE0000000740\" with container ID \"ctr-clr\"" + ], + "rawOutput": " Scenario: clear_lifecycle_registry stops health check threads \n Given a mock devcontainer CLI runner\n And the runner configured for successful health check\n And an active container \"01TESTLIFECYCLE0000000740\" with container ID \"ctr-clr\"" + }, + { + "name": "Output Block 44", + "passed": true, + "output": [ + " Scenario: resource list shows \"discovered (not built)\" for a new devcontainer-instance ", + " Given a fresh in-memory resource registry", + " And a running health check for \"01TESTLIFECYCLE0000000740\"", + " When I clear the lifecycle registry", + " Then the registry should be empty", + " And the health check for \"01TESTLIFECYCLE0000000740\" should be unregistered" + ], + "rawOutput": " Scenario: resource list shows \"discovered (not built)\" for a new devcontainer-instance \n Given a fresh in-memory resource registry\n And a running health check for \"01TESTLIFECYCLE0000000740\"\n When I clear the lifecycle registry\n Then the registry should be empty\n And the health check for \"01TESTLIFECYCLE0000000740\" should be unregistered" + }, + { + "name": "Output Block 45", + "passed": false, + "output": [ + "1 feature passed, 0 failed, 0 skipped", + "32 scenarios passed, 0 failed, 0 skipped", + "106 steps passed, 0 failed, 0 skipped", + "Took 0min 0.037s", + " And built-in types are bootstrapped", + " Given a devcontainer-instance resource is registered", + " When I run resource list with all flag", + " Then the resource output should contain \"discovered (not built)\"" + ], + "rawOutput": "1 feature passed, 0 failed, 0 skipped\n32 scenarios passed, 0 failed, 0 skipped\n106 steps passed, 0 failed, 0 skipped\nTook 0min 0.037s\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n When I run resource list with all flag\n Then the resource output should contain \"discovered (not built)\"" + }, + { + "name": "Output Block 46", + "passed": true, + "output": [ + " Scenario: resource list shows \"running\" state for a running devcontainer ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given a devcontainer-instance resource is registered", + " And the devcontainer lifecycle state is \"running\"", + " When I run resource list with all flag", + " Then the resource output should contain \"running\"" + ], + "rawOutput": " Scenario: resource list shows \"running\" state for a running devcontainer \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n And the devcontainer lifecycle state is \"running\"\n When I run resource list with all flag\n Then the resource output should contain \"running\"" + }, + { + "name": "Output Block 47", + "passed": true, + "output": [ + " Scenario: resource list shows \"stopped\" state for a stopped devcontainer ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given a devcontainer-instance resource is registered", + " And the devcontainer lifecycle state is \"stopped\"", + " When I run resource list with all flag", + " Then the resource output should contain \"stopped\"" + ], + "rawOutput": " Scenario: resource list shows \"stopped\" state for a stopped devcontainer \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n And the devcontainer lifecycle state is \"stopped\"\n When I run resource list with all flag\n Then the resource output should contain \"stopped\"" + }, + { + "name": "Output Block 48", + "passed": false, + "output": [ + " Scenario: resource list shows \"failed\" state for a failed devcontainer ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given a devcontainer-instance resource is registered", + " And the devcontainer lifecycle state is \"failed\"", + " When I run resource list with all flag", + " Then the resource output should contain \"failed\"" + ], + "rawOutput": " Scenario: resource list shows \"failed\" state for a failed devcontainer \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n And the devcontainer lifecycle state is \"failed\"\n When I run resource list with all flag\n Then the resource output should contain \"failed\"" + }, + { + "name": "Output Block 49", + "passed": true, + "output": [ + " Scenario: resource list shows empty state for non-container resources ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given I run resource add \"git-checkout\" \"local/my-repo\" with path \"/tmp/repo\"", + " When I run resource list", + " Then the resource output should contain \"local/my-repo\"", + " And the resource output should contain \"git-checkout\"" + ], + "rawOutput": " Scenario: resource list shows empty state for non-container resources \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given I run resource add \"git-checkout\" \"local/my-repo\" with path \"/tmp/repo\"\n When I run resource list\n Then the resource output should contain \"local/my-repo\"\n And the resource output should contain \"git-checkout\"" + }, + { + "name": "Output Block 50", + "passed": true, + "output": [ + " Scenario: resource list shows warning banner when devcontainer is in detected state ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given a devcontainer-instance resource is registered", + " When I run resource list with all flag", + " Then the resource output should contain \"Devcontainer detected\"" + ], + "rawOutput": " Scenario: resource list shows warning banner when devcontainer is in detected state \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n When I run resource list with all flag\n Then the resource output should contain \"Devcontainer detected\"" + }, + { + "name": "FAIL: Output Block 51", + "passed": false, + "output": [ + " ASSERT FAILED: Expected 'Devcontainer detected' in output, got:", + " Resources (1 total) ", + " ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓", + " ┃ ID ┃ Name ┃ Type ┃ Status ┃ Kind ┃ Location ┃ Description ┃", + " ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩", + " │ 01KP2TDV4GAR... │ (unnamed) │ devcontainer-instance │ discovered (not built) │ physical │ /tmp/test-project │ Test devcontainer │", + " └─────────────────┴───────────┴───────────────────────┴────────────────────────┴──────────┴───────────────────┴───────────────────┘" + ], + "rawOutput": " ASSERT FAILED: Expected 'Devcontainer detected' in output, got:\n Resources (1 total) \n ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓\n ┃ ID ┃ Name ┃ Type ┃ Status ┃ Kind ┃ Location ┃ Description ┃\n ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩\n │ 01KP2TDV4GAR... │ (unnamed) │ devcontainer-instance │ discovered (not built) │ physical │ /tmp/test-project │ Test devcontainer │\n └─────────────────┴───────────┴───────────────────────┴────────────────────────┴──────────┴───────────────────┴───────────────────┘" + }, + { + "name": "Output Block 52", + "passed": true, + "output": [ + "", + " Scenario: resource list does not show warning banner when devcontainer is running ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given a devcontainer-instance resource is registered", + " And the devcontainer lifecycle state is \"running\"", + " When I run resource list with all flag", + " Then the resource output should not contain \"Devcontainer detected\"" + ], + "rawOutput": "\n Scenario: resource list does not show warning banner when devcontainer is running \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n And the devcontainer lifecycle state is \"running\"\n When I run resource list with all flag\n Then the resource output should not contain \"Devcontainer detected\"" + }, + { + "name": "Output Block 53", + "passed": false, + "output": [ + " @tdd_issue @tdd_issue_4263 @tdd_expected_fail", + " Scenario: resource list JSON output includes lifecycle_state for devcontainer-instance ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given a devcontainer-instance resource is registered", + " When I run resource list with all flag and format \"json\"", + " Then the resource output should be valid JSON", + " And the resource JSON output should contain key \"lifecycle_state\"" + ], + "rawOutput": " @tdd_issue @tdd_issue_4263 @tdd_expected_fail\n Scenario: resource list JSON output includes lifecycle_state for devcontainer-instance \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n When I run resource list with all flag and format \"json\"\n Then the resource output should be valid JSON\n And the resource JSON output should contain key \"lifecycle_state\"" + }, + { + "name": "FAIL: Output Block 54", + "passed": false, + "output": [ + " ASSERT FAILED: Key 'lifecycle_state' not found in JSON output" + ], + "rawOutput": " ASSERT FAILED: Key 'lifecycle_state' not found in JSON output" + }, + { + "name": "Output Block 55", + "passed": true, + "output": [ + "", + " Scenario: resource list JSON output has null lifecycle_state for non-container resources ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given I run resource add \"git-checkout\" \"local/my-repo\" with path \"/tmp/repo\"", + " When I run resource list with format \"json\"", + " Then the resource output should be valid JSON", + " And the resource JSON lifecycle_state should be null for non-container resources" + ], + "rawOutput": "\n Scenario: resource list JSON output has null lifecycle_state for non-container resources \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given I run resource add \"git-checkout\" \"local/my-repo\" with path \"/tmp/repo\"\n When I run resource list with format \"json\"\n Then the resource output should be valid JSON\n And the resource JSON lifecycle_state should be null for non-container resources" + }, + { + "name": "Output Block 56", + "passed": true, + "output": [ + " Scenario: resource list shows lifecycle state for container-instance resources ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + " Given a container-instance resource is registered", + " When I run resource list with all flag", + " Then the resource output should contain \"Status\"", + " And the resource output should contain \"discovered (not built)\"" + ], + "rawOutput": " Scenario: resource list shows lifecycle state for container-instance resources \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a container-instance resource is registered\n When I run resource list with all flag\n Then the resource output should contain \"Status\"\n And the resource output should contain \"discovered (not built)\"" + }, + { + "name": "Output Block 57", + "passed": false, + "output": [ + " Scenario: resource list succeeds gracefully when lifecycle tracker raises an error ", + " Given a fresh in-memory resource registry", + " And built-in types are bootstrapped", + "Feature: Devcontainer Resource Handler", + " As a CleverAgents developer", + " I want devcontainer resources to be auto-discovered and manually registered", + " So that devcontainer environments integrate into the resource model Given a devcontainer-instance resource is registered" + ], + "rawOutput": " Scenario: resource list succeeds gracefully when lifecycle tracker raises an error \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\nFeature: Devcontainer Resource Handler\n As a CleverAgents developer\n I want devcontainer resources to be auto-discovered and manually registered\n So that devcontainer environments integrate into the resource model Given a devcontainer-instance resource is registered" + }, + { + "name": "Output Block 58", + "passed": false, + "output": [ + " Scenario: Register devcontainer-instance manually ", + " Given a temporary directory with a valid devcontainer.json", + " When I create a devcontainer-instance resource at that path", + " Then And the resource should have type \"the lifecycle tracker raises an error for the resourcedevcontainer-instance\"" + ], + "rawOutput": " Scenario: Register devcontainer-instance manually \n Given a temporary directory with a valid devcontainer.json\n When I create a devcontainer-instance resource at that path\n Then And the resource should have type \"the lifecycle tracker raises an error for the resourcedevcontainer-instance\"" + }, + { + "name": "Output Block 59", + "passed": true, + "output": [ + " And the resource should have a non-empty resource_id", + " When I run resource list with all flag", + " Then the resource list command should succeed" + ], + "rawOutput": " And the resource should have a non-empty resource_id\n When I run resource list with all flag\n Then the resource list command should succeed" + }, + { + "name": "Output Block 60", + "passed": true, + "output": [ + " Scenario: Register container-instance manually ", + " When I create a container-instance resource with image \"ubuntu:latest\"", + " Then the container resource should have type \"container-instance\"", + " And the resource output should contain \"devcontainer-instance\"" + ], + "rawOutput": " Scenario: Register container-instance manually \n When I create a container-instance resource with image \"ubuntu:latest\"\n Then the container resource should have type \"container-instance\"\n And the resource output should contain \"devcontainer-instance\"" + }, + { + "name": "Output Block 61", + "passed": false, + "output": [ + "", + "Failing scenarios:", + " features/resource_list_lifecycle_state.feature:44 resource list shows warning banner when devcontainer is in detected state" + ], + "rawOutput": "\nFailing scenarios:\n features/resource_list_lifecycle_state.feature:44 resource list shows warning banner when devcontainer is in detected state" + }, + { + "name": "Output Block 62", + "passed": false, + "output": [ + "0 features passed, 1 failed, 0 skipped", + "11 scenarios passed, 1 failed, 0 skipped", + "69 steps passed, 1 failed, 0 skipped", + "Took 0min 1.827s" + ], + "rawOutput": "0 features passed, 1 failed, 0 skipped\n11 scenarios passed, 1 failed, 0 skipped\n69 steps passed, 1 failed, 0 skipped\nTook 0min 1.827s" + }, + { + "name": "Output Block 63", + "passed": true, + "output": [ + " Scenario: Auto-discover devcontainer from git-checkout resource ", + " Given a temporary directory simulating a git checkout", + " And the directory has a .devcontainer/devcontainer.json file", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 1 result", + " And the discovered config path should end with \"devcontainer.json\"" + ], + "rawOutput": " Scenario: Auto-discover devcontainer from git-checkout resource \n Given a temporary directory simulating a git checkout\n And the directory has a .devcontainer/devcontainer.json file\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 1 result\n And the discovered config path should end with \"devcontainer.json\"" + }, + { + "name": "Output Block 64", + "passed": true, + "output": [ + " Scenario: Auto-discover root devcontainer.json from git-checkout ", + " Given a temporary directory simulating a git checkout", + " And the directory has a root .devcontainer.json file", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 1 result" + ], + "rawOutput": " Scenario: Auto-discover root devcontainer.json from git-checkout \n Given a temporary directory simulating a git checkout\n And the directory has a root .devcontainer.json file\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 1 result" + }, + { + "name": "Output Block 65", + "passed": true, + "output": [ + " Scenario: Auto-discover both devcontainer locations ", + " Given a temporary directory simulating a git checkout", + " And the directory has a .devcontainer/devcontainer.json file", + " And the directory has a root .devcontainer.json file", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 2 results" + ], + "rawOutput": " Scenario: Auto-discover both devcontainer locations \n Given a temporary directory simulating a git checkout\n And the directory has a .devcontainer/devcontainer.json file\n And the directory has a root .devcontainer.json file\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 2 results" + }, + { + "name": "Output Block 66", + "passed": true, + "output": [ + " Scenario: Auto-discover devcontainer from fs-directory resource ", + " Given a temporary directory simulating a filesystem mount", + " And the directory has a .devcontainer/devcontainer.json file", + " When I run devcontainer discovery on the directory as \"fs-directory\"", + " Then discovery should return 1 result" + ], + "rawOutput": " Scenario: Auto-discover devcontainer from fs-directory resource \n Given a temporary directory simulating a filesystem mount\n And the directory has a .devcontainer/devcontainer.json file\n When I run devcontainer discovery on the directory as \"fs-directory\"\n Then discovery should return 1 result" + }, + { + "name": "Output Block 67", + "passed": true, + "output": [ + " Scenario: No discovery for non-trigger resource types ", + " Given a temporary directory simulating a filesystem mount", + " And the directory has a .devcontainer/devcontainer.json file", + " When I run devcontainer discovery on the directory as \"fs-file\"", + " Then discovery should return 0 results" + ], + "rawOutput": " Scenario: No discovery for non-trigger resource types \n Given a temporary directory simulating a filesystem mount\n And the directory has a .devcontainer/devcontainer.json file\n When I run devcontainer discovery on the directory as \"fs-file\"\n Then discovery should return 0 results" + }, + { + "name": "Output Block 68", + "passed": true, + "output": [ + " Scenario: Skip invalid JSON in devcontainer.json ", + " Given a temporary directory with an invalid devcontainer.json", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 0 results" + ], + "rawOutput": " Scenario: Skip invalid JSON in devcontainer.json \n Given a temporary directory with an invalid devcontainer.json\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 0 results" + }, + { + "name": "Output Block 69", + "passed": true, + "output": [ + " Scenario: Skip non-object JSON in devcontainer.json ", + " Given a temporary directory with a non-object devcontainer.json", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 0 results" + ], + "rawOutput": " Scenario: Skip non-object JSON in devcontainer.json \n Given a temporary directory with a non-object devcontainer.json\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 0 results" + }, + { + "name": "Output Block 70", + "passed": true, + "output": [ + " Scenario: Handle missing devcontainer directory gracefully ", + " Given a temporary directory with no devcontainer configuration", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 0 results", + "@clone-into", + "Feature: container-instance --clone-into argument", + " As a CleverAgents user", + " I want to clone a git repository into a running container", + " So that I can work with the repository inside the container environment", + " Scenario: Devcontainer types appear in resource type list ", + " Given the built-in resource type registry" + ], + "rawOutput": " Scenario: Handle missing devcontainer directory gracefully \n Given a temporary directory with no devcontainer configuration\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 0 results\n@clone-into\nFeature: container-instance --clone-into argument\n As a CleverAgents user\n I want to clone a git repository into a running container\n So that I can work with the repository inside the container environment\n Scenario: Devcontainer types appear in resource type list \n Given the built-in resource type registry" + }, + { + "name": "Output Block 71", + "passed": true, + "output": [ + " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.1 ", + " When I validate clone-into URL \"https://github.com/org/repo.git\"", + " When I list all built-in type names", + " Then the clone-into URL should be valid", + " Then the type list should contain \"devcontainer-instance\"", + " And the type list should contain \"devcontainer-file\"", + " And the type list should contain \"container-instance\"" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.1 \n When I validate clone-into URL \"https://github.com/org/repo.git\"\n When I list all built-in type names\n Then the clone-into URL should be valid\n Then the type list should contain \"devcontainer-instance\"\n And the type list should contain \"devcontainer-file\"\n And the type list should contain \"container-instance\"" + }, + { + "name": "Output Block 72", + "passed": true, + "output": [ + " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.2 ", + " When I validate clone-into URL \"http://internal.example.com/repo.git\"", + " Then the clone-into URL should be valid" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.2 \n When I validate clone-into URL \"http://internal.example.com/repo.git\"\n Then the clone-into URL should be valid" + }, + { + "name": "Output Block 73", + "passed": true, + "output": [ + " Scenario: DevcontainerHandler satisfies ResourceHandler protocol ", + " When I instantiate DevcontainerHandler", + " Then it should satisfy the ResourceHandler protocol" + ], + "rawOutput": " Scenario: DevcontainerHandler satisfies ResourceHandler protocol \n When I instantiate DevcontainerHandler\n Then it should satisfy the ResourceHandler protocol" + }, + { + "name": "Output Block 74", + "passed": true, + "output": [ + " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.3 ", + " When I validate clone-into URL \"git@github.com:org/repo.git\"", + " Then the clone-into URL should be valid" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.3 \n When I validate clone-into URL \"git@github.com:org/repo.git\"\n Then the clone-into URL should be valid" + }, + { + "name": "Output Block 75", + "passed": true, + "output": [ + " Scenario: DevcontainerHandler has correct default strategy ", + " When I instantiate DevcontainerHandler" + ], + "rawOutput": " Scenario: DevcontainerHandler has correct default strategy \n When I instantiate DevcontainerHandler" + }, + { + "name": "Output Block 76", + "passed": true, + "output": [ + " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.4 ", + " When I validate clone-into URL \"ssh://git@bitbucket.org/org/repo.git\"", + " Then the clone-into URL should be valid", + " Then its default strategy should be \"snapshot\"" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.4 \n When I validate clone-into URL \"ssh://git@bitbucket.org/org/repo.git\"\n Then the clone-into URL should be valid\n Then its default strategy should be \"snapshot\"" + }, + { + "name": "Output Block 77", + "passed": true, + "output": [ + " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.5 ", + " When I validate clone-into URL \"git://github.com/org/repo.git\"" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.5 \n When I validate clone-into URL \"git://github.com/org/repo.git\"" + }, + { + "name": "Output Block 78", + "passed": true, + "output": [ + " Scenario: DevcontainerHandler has correct type label ", + " When I instantiate DevcontainerHandler", + " Then the clone-into URL should be valid", + " Then its type label should be \"devcontainer\"" + ], + "rawOutput": " Scenario: DevcontainerHandler has correct type label \n When I instantiate DevcontainerHandler\n Then the clone-into URL should be valid\n Then its type label should be \"devcontainer\"" + }, + { + "name": "Output Block 79", + "passed": true, + "output": [ + " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.6 ", + " When I validate clone-into URL \"/local/path/to/repo\"", + " Then the clone-into URL should be valid" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.6 \n When I validate clone-into URL \"/local/path/to/repo\"\n Then the clone-into URL should be valid" + }, + { + "name": "Output Block 80", + "passed": true, + "output": [ + " Scenario: Discovery result requires valid config_path ", + " When I create a discovery result with valid parameters", + " Then the result should have a config_path attribute" + ], + "rawOutput": " Scenario: Discovery result requires valid config_path \n When I create a discovery result with valid parameters\n Then the result should have a config_path attribute" + }, + { + "name": "Output Block 81", + "passed": true, + "output": [ + " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.7 ", + " When I validate clone-into URL \"./relative/repo\"", + " Then the clone-into URL should be valid" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.7 \n When I validate clone-into URL \"./relative/repo\"\n Then the clone-into URL should be valid" + }, + { + "name": "Output Block 82", + "passed": true, + "output": [ + " Scenario: Discovery result rejects empty parent_location ", + " When I create a discovery result with empty parent_location" + ], + "rawOutput": " Scenario: Discovery result rejects empty parent_location \n When I create a discovery result with empty parent_location" + }, + { + "name": "Output Block 83", + "passed": false, + "output": [ + " Scenario Outline: validate_clone_into_url rejects invalid URLs -- @1.1 ", + " When I validate clone-into URL \"not-a-url\"", + " Then it should raise a ValueError", + " Then the clone-into URL should be invalid" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url rejects invalid URLs -- @1.1 \n When I validate clone-into URL \"not-a-url\"\n Then it should raise a ValueError\n Then the clone-into URL should be invalid" + }, + { + "name": "Output Block 84", + "passed": true, + "output": [ + " Scenario Outline: validate_clone_into_url rejects invalid URLs -- @1.2 ", + " When I validate clone-into URL \"ftp://example.com\"" + ], + "rawOutput": " Scenario Outline: validate_clone_into_url rejects invalid URLs -- @1.2 \n When I validate clone-into URL \"ftp://example.com\"" + }, + { + "name": "Output Block 85", + "passed": true, + "output": [ + " Scenario: Auto-discover named devcontainer configuration ", + " Given a temporary directory simulating a git checkout", + " Then the clone-into URL should be invalid", + " And the directory has a named devcontainer configuration \"api\"", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 1 result", + " And the first result should have config name \"api\"" + ], + "rawOutput": " Scenario: Auto-discover named devcontainer configuration \n Given a temporary directory simulating a git checkout\n Then the clone-into URL should be invalid\n And the directory has a named devcontainer configuration \"api\"\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 1 result\n And the first result should have config name \"api\"" + }, + { + "name": "Output Block 86", + "passed": false, + "output": [ + " Scenario: clone_repo_into_container rejects empty container_id ", + " When I call clone_repo_into_container with empty container_id", + " Then it should raise ValueError mentioning \"container_id\"" + ], + "rawOutput": " Scenario: clone_repo_into_container rejects empty container_id \n When I call clone_repo_into_container with empty container_id\n Then it should raise ValueError mentioning \"container_id\"" + }, + { + "name": "Output Block 87", + "passed": true, + "output": [ + " Scenario: Auto-discover multiple named devcontainer configurations ", + " Given a temporary directory simulating a git checkout", + " And the directory has a named devcontainer configuration \"api\"" + ], + "rawOutput": " Scenario: Auto-discover multiple named devcontainer configurations \n Given a temporary directory simulating a git checkout\n And the directory has a named devcontainer configuration \"api\"" + }, + { + "name": "Output Block 88", + "passed": false, + "output": [ + " Scenario: clone_repo_into_container rejects empty repo_url ", + " When I call clone_repo_into_container with empty repo_url", + " And the directory has a named devcontainer configuration \"frontend\"", + " Then it should raise ValueError mentioning \"repo_url\"", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 2 results", + " And the result config names should include \"api\"" + ], + "rawOutput": " Scenario: clone_repo_into_container rejects empty repo_url \n When I call clone_repo_into_container with empty repo_url\n And the directory has a named devcontainer configuration \"frontend\"\n Then it should raise ValueError mentioning \"repo_url\"\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 2 results\n And the result config names should include \"api\"" + }, + { + "name": "Output Block 89", + "passed": true, + "output": [ + " Scenario: clone_repo_into_container succeeds when docker exec returns 0 ", + " Given a mock docker exec that succeeds for clone", + " And the result config names should include \"frontend\"", + " When I clone \"https://github.com/org/repo.git\" into container \"abc123def456\"", + " Then the clone should succeed", + " And the clone target should be \"/workspace\"" + ], + "rawOutput": " Scenario: clone_repo_into_container succeeds when docker exec returns 0 \n Given a mock docker exec that succeeds for clone\n And the result config names should include \"frontend\"\n When I clone \"https://github.com/org/repo.git\" into container \"abc123def456\"\n Then the clone should succeed\n And the clone target should be \"/workspace\"" + }, + { + "name": "Output Block 90", + "passed": true, + "output": [ + " Scenario: Auto-discover mixed root and named devcontainer configurations ", + " Given a temporary directory simulating a git checkout", + " And the directory has a .devcontainer/devcontainer.json file" + ], + "rawOutput": " Scenario: Auto-discover mixed root and named devcontainer configurations \n Given a temporary directory simulating a git checkout\n And the directory has a .devcontainer/devcontainer.json file" + }, + { + "name": "Output Block 91", + "passed": true, + "output": [ + " Scenario: clone_repo_into_container uses custom target directory ", + " Given a mock docker exec that succeeds for clone", + " And the directory has a named devcontainer configuration \"api\"", + " When I clone repo \"https://github.com/org/repo.git\" into container \"abc123def456\" with target \"/custom/dir\"", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then the clone should succeed", + " And the clone target should be \"/custom/dir\"", + " Then discovery should return 2 results" + ], + "rawOutput": " Scenario: clone_repo_into_container uses custom target directory \n Given a mock docker exec that succeeds for clone\n And the directory has a named devcontainer configuration \"api\"\n When I clone repo \"https://github.com/org/repo.git\" into container \"abc123def456\" with target \"/custom/dir\"\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then the clone should succeed\n And the clone target should be \"/custom/dir\"\n Then discovery should return 2 results" + }, + { + "name": "Output Block 92", + "passed": true, + "output": [ + " Scenario: Root devcontainer.json has no config name ", + " Given a temporary directory simulating a git checkout", + " And the directory has a .devcontainer/devcontainer.json file" + ], + "rawOutput": " Scenario: Root devcontainer.json has no config name \n Given a temporary directory simulating a git checkout\n And the directory has a .devcontainer/devcontainer.json file" + }, + { + "name": "Output Block 93", + "passed": false, + "output": [ + " Scenario: clone_repo_into_container raises CloneIntoError when docker exec fails ", + " Given a mock docker exec that fails for clone with stderr \"fatal: repository not found\"", + " When I clone \"https://github.com/org/missing.git\" into container \"abc123def456\"", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then the clone should raise CloneIntoError", + " And the CloneIntoError should mention \"repository not found\"", + " Then discovery should return 1 result", + " And the first result should have no config name" + ], + "rawOutput": " Scenario: clone_repo_into_container raises CloneIntoError when docker exec fails \n Given a mock docker exec that fails for clone with stderr \"fatal: repository not found\"\n When I clone \"https://github.com/org/missing.git\" into container \"abc123def456\"\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then the clone should raise CloneIntoError\n And the CloneIntoError should mention \"repository not found\"\n Then discovery should return 1 result\n And the first result should have no config name" + }, + { + "name": "Output Block 94", + "passed": false, + "output": [ + " Scenario: CloneIntoError stores repo_url, container_id, and stderr ", + " Given a CloneIntoError with repo \"https://example.com/repo.git\" container \"ctr-001\" stderr \"auth failed\"", + " Then the CloneIntoError repo_url should be \"https://example.com/repo.git\"", + " And the CloneIntoError container_id should be \"ctr-001\"", + " And the CloneIntoError stderr should be \"auth failed\"" + ], + "rawOutput": " Scenario: CloneIntoError stores repo_url, container_id, and stderr \n Given a CloneIntoError with repo \"https://example.com/repo.git\" container \"ctr-001\" stderr \"auth failed\"\n Then the CloneIntoError repo_url should be \"https://example.com/repo.git\"\n And the CloneIntoError container_id should be \"ctr-001\"\n And the CloneIntoError stderr should be \"auth failed\"" + }, + { + "name": "Output Block 95", + "passed": true, + "output": [ + " Scenario: Root .devcontainer.json has no config name ", + " Given a temporary directory simulating a git checkout", + " And the directory has a root .devcontainer.json file", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 1 result", + " And ", + "the first result should have no config name Scenario: container-instance resource type has clone-into CLI argument " + ], + "rawOutput": " Scenario: Root .devcontainer.json has no config name \n Given a temporary directory simulating a git checkout\n And the directory has a root .devcontainer.json file\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 1 result\n And \nthe first result should have no config name Scenario: container-instance resource type has clone-into CLI argument " + }, + { + "name": "Output Block 96", + "passed": true, + "output": [ + " When I look up the \"container-instance\" resource type spec", + " Then the spec should have a CLI argument named \"clone-into\"", + " And the clone-into argument should be of type \"string\"", + " And the clone-into argument should not be required" + ], + "rawOutput": " When I look up the \"container-instance\" resource type spec\n Then the spec should have a CLI argument named \"clone-into\"\n And the clone-into argument should be of type \"string\"\n And the clone-into argument should not be required" + }, + { + "name": "Output Block 97", + "passed": true, + "output": [ + " Scenario: Named config with invalid JSON is skipped ", + " Given a temporary directory simulating a git checkout", + " And the directory has a named devcontainer configuration \"broken\" with invalid JSON" + ], + "rawOutput": " Scenario: Named config with invalid JSON is skipped \n Given a temporary directory simulating a git checkout\n And the directory has a named devcontainer configuration \"broken\" with invalid JSON" + }, + { + "name": "Output Block 98", + "passed": true, + "output": [ + " Scenario: validate_clone_into_url rejects whitespace-only string ", + " When I validate clone-into URL with whitespace only", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then the clone-into URL should be invalid" + ], + "rawOutput": " Scenario: validate_clone_into_url rejects whitespace-only string \n When I validate clone-into URL with whitespace only\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then the clone-into URL should be invalid" + }, + { + "name": "Output Block 99", + "passed": false, + "output": [ + "1 feature passed, 0 failed, 0 skipped", + "17 scenarios passed, 0 failed, 0 skipped", + "44 steps passed, 0 failed, 0 skipped", + "Took 0min 0.008s", + " Then discovery should return 0 results" + ], + "rawOutput": "1 feature passed, 0 failed, 0 skipped\n17 scenarios passed, 0 failed, 0 skipped\n44 steps passed, 0 failed, 0 skipped\nTook 0min 0.008s\n Then discovery should return 0 results" + }, + { + "name": "Output Block 100", + "passed": true, + "output": [ + " Scenario: Empty .devcontainer directory returns no results ", + " Given a temporary directory simulating a git checkout", + " And the directory has an empty .devcontainer directory", + " When I run devcontainer discovery on the directory as \"git-checkout\"", + " Then discovery should return 0 results" + ], + "rawOutput": " Scenario: Empty .devcontainer directory returns no results \n Given a temporary directory simulating a git checkout\n And the directory has an empty .devcontainer directory\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 0 results" + }, + { + "name": "Output Block 101", + "passed": true, + "output": [ + " Scenario: git-checkout is a trigger type ", + " Then \"git-checkout\" should be a trigger type" + ], + "rawOutput": " Scenario: git-checkout is a trigger type \n Then \"git-checkout\" should be a trigger type" + }, + { + "name": "Output Block 102", + "passed": true, + "output": [ + " Scenario: fs-directory is a trigger type ", + " Then \"fs-directory\" should be a trigger type" + ], + "rawOutput": " Scenario: fs-directory is a trigger type \n Then \"fs-directory\" should be a trigger type" + }, + { + "name": "Output Block 103", + "passed": true, + "output": [ + " Scenario: devcontainer-instance is not a trigger type ", + " Then \"devcontainer-instance\" should not be a trigger type" + ], + "rawOutput": " Scenario: devcontainer-instance is not a trigger type \n Then \"devcontainer-instance\" should not be a trigger type" + }, + { + "name": "Output Block 104", + "passed": false, + "output": [ + "1 feature passed, 0 failed, 0 skipped", + "26 scenarios passed, 0 failed, 0 skipped", + "90 steps passed, 0 failed, 0 skipped", + "Took 0min 0.026s", + "Feature: Devcontainer Session Cleanup and CLI Commands", + " As a CleverAgents developer", + " I want session-scoped container cleanup and CLI stop/rebuild commands", + " So that containers are cleaned up when sessions end and can be managed via CLI", + " Scenario: Active containers stopped on session cleanup ", + " Given a mock devcontainer CLI runner", + " And an active container \"01TESTLIFECYCLE0000000040\" with container ID \"ctr-a\"", + " And an active container \"01TESTLIFECYCLE0000000041\" with container ID \"ctr-b\"", + " When I run session cleanup for session \"session-001\"", + " Then the container state for \"01TESTLIFECYCLE0000000040\" should be \"stopped\"", + " And the container state for \"01TESTLIFECYCLE0000000041\" should be \"stopped\"" + ], + "rawOutput": "1 feature passed, 0 failed, 0 skipped\n26 scenarios passed, 0 failed, 0 skipped\n90 steps passed, 0 failed, 0 skipped\nTook 0min 0.026s\nFeature: Devcontainer Session Cleanup and CLI Commands\n As a CleverAgents developer\n I want session-scoped container cleanup and CLI stop/rebuild commands\n So that containers are cleaned up when sessions end and can be managed via CLI\n Scenario: Active containers stopped on session cleanup \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000040\" with container ID \"ctr-a\"\n And an active container \"01TESTLIFECYCLE0000000041\" with container ID \"ctr-b\"\n When I run session cleanup for session \"session-001\"\n Then the container state for \"01TESTLIFECYCLE0000000040\" should be \"stopped\"\n And the container state for \"01TESTLIFECYCLE0000000041\" should be \"stopped\"" + }, + { + "name": "Output Block 105", + "passed": true, + "output": [ + " Scenario: CleanupService stop_active_devcontainers is callable and returns list ", + " When I call CleanupService stop_active_devcontainers with no active containers", + " Then the cleanup result should be an empty list", + " And CleanupService should have a stop_active_devcontainers method" + ], + "rawOutput": " Scenario: CleanupService stop_active_devcontainers is callable and returns list \n When I call CleanupService stop_active_devcontainers with no active containers\n Then the cleanup result should be an empty list\n And CleanupService should have a stop_active_devcontainers method" + }, + { + "name": "Output Block 106", + "passed": true, + "output": [ + " Scenario: CleanupService stop_active_devcontainers calls handler cleanup ", + " When I call CleanupService stop_active_devcontainers with no active containers", + " Then the cleanup result should be an empty list" + ], + "rawOutput": " Scenario: CleanupService stop_active_devcontainers calls handler cleanup \n When I call CleanupService stop_active_devcontainers with no active containers\n Then the cleanup result should be an empty list" + }, + { + "name": "Output Block 107", + "passed": false, + "output": [ + " Scenario: Session cleanup handles stop failure gracefully ", + " Given a mock devcontainer CLI runner", + " And the runner configured to raise exception on stop", + " And an active container \"01TESTLIFECYCLE0000000130\" with container ID \"ctr-fail\"", + " When I run session cleanup for session \"session-err\"", + " Then the cleanup should return 0 stopped containers" + ], + "rawOutput": " Scenario: Session cleanup handles stop failure gracefully \n Given a mock devcontainer CLI runner\n And the runner configured to raise exception on stop\n And an active container \"01TESTLIFECYCLE0000000130\" with container ID \"ctr-fail\"\n When I run session cleanup for session \"session-err\"\n Then the cleanup should return 0 stopped containers" + }, + { + "name": "Output Block 108", + "passed": true, + "output": [ + " Scenario: CLI stop succeeds for active devcontainer-instance ", + " Given a mock resource service with devcontainer \"local/test-dc\" id \"01TESTLIFECYCLE0000000200\" at \"/workspace/project\"", + " And an active container \"01TESTLIFECYCLE0000000200\" with container ID \"ctr-cli-stop\"", + " When I invoke CLI resource stop \"local/test-dc\"", + "Feature: DevcontainerHandler missing protocol methods", + " As a developer using DevcontainerHandler", + " I want delete(), list_children(), diff(), and create_sandbox() to be implemented", + " So that the handler satisfies the full ResourceHandler protocol (issue #1242)", + " Scenario: dcproto delete file successfully from devcontainer ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto subprocess returns successful rm output", + " When dcproto I delete path \"src/old_file.py\" from the resource", + " Then dcproto the delete should succeed", + " And dcproto the delete message should contain \"src/old_file.py\"", + " And dcproto the subprocess should have been called with \"rm\" and \"src/old_file.py\"" + ], + "rawOutput": " Scenario: CLI stop succeeds for active devcontainer-instance \n Given a mock resource service with devcontainer \"local/test-dc\" id \"01TESTLIFECYCLE0000000200\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000200\" with container ID \"ctr-cli-stop\"\n When I invoke CLI resource stop \"local/test-dc\"\nFeature: DevcontainerHandler missing protocol methods\n As a developer using DevcontainerHandler\n I want delete(), list_children(), diff(), and create_sandbox() to be implemented\n So that the handler satisfies the full ResourceHandler protocol (issue #1242)\n Scenario: dcproto delete file successfully from devcontainer \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful rm output\n When dcproto I delete path \"src/old_file.py\" from the resource\n Then dcproto the delete should succeed\n And dcproto the delete message should contain \"src/old_file.py\"\n And dcproto the subprocess should have been called with \"rm\" and \"src/old_file.py\"" + }, + { + "name": "Output Block 109", + "passed": true, + "output": [ + " Scenario: dcproto delete with empty path targets workspace root ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto subprocess returns successful rm output", + " When dcproto I delete with empty path from the resource", + " Then dcproto the delete should succeed", + " And dcproto the subprocess should have been called with \"rm\" and \".\"" + ], + "rawOutput": " Scenario: dcproto delete with empty path targets workspace root \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful rm output\n When dcproto I delete with empty path from the resource\n Then dcproto the delete should succeed\n And dcproto the subprocess should have been called with \"rm\" and \".\"" + }, + { + "name": "Output Block 110", + "passed": false, + "output": [ + " Scenario: dcproto delete fails when container is stopped ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto subprocess returns failed rm output with stderr \"container not running\"", + " When dcproto I delete path \"file.txt\" from the resource", + " Then the CLI exit code should be 0", + " And the CLI output should contain \"Stopped\"", + " Then dcproto the delete should fail", + " And dcproto the delete failure message should contain \"container not running\"" + ], + "rawOutput": " Scenario: dcproto delete fails when container is stopped \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns failed rm output with stderr \"container not running\"\n When dcproto I delete path \"file.txt\" from the resource\n Then the CLI exit code should be 0\n And the CLI output should contain \"Stopped\"\n Then dcproto the delete should fail\n And dcproto the delete failure message should contain \"container not running\"" + }, + { + "name": "Output Block 111", + "passed": true, + "output": [ + " Scenario: CLI stop rejects non-devcontainer resource type ", + " Given a mock resource service with git-checkout \"local/my-repo\" id \"01TESTLIFECYCLE0000000201\"", + " When I invoke CLI resource stop \"local/my-repo\"" + ], + "rawOutput": " Scenario: CLI stop rejects non-devcontainer resource type \n Given a mock resource service with git-checkout \"local/my-repo\" id \"01TESTLIFECYCLE0000000201\"\n When I invoke CLI resource stop \"local/my-repo\"" + }, + { + "name": "Output Block 112", + "passed": false, + "output": [ + " Scenario: dcproto delete from resource with no location raises ValueError ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with no location", + " When dcproto I delete path \"any/file.txt\" from the resource", + " Then dcproto the delete should raise ValueError containing \"no location\"" + ], + "rawOutput": " Scenario: dcproto delete from resource with no location raises ValueError \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with no location\n When dcproto I delete path \"any/file.txt\" from the resource\n Then dcproto the delete should raise ValueError containing \"no location\"" + }, + { + "name": "Output Block 113", + "passed": true, + "output": [ + " Scenario: dcproto list_children returns sorted names from devcontainer ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto subprocess returns successful ls listing \"src\\ntests\\nREADME.md\"", + " When dcproto I list children of the resource", + " Then dcproto the children list should have 3 entries", + " And dcproto the children list should be \"README.md,src,tests\"" + ], + "rawOutput": " Scenario: dcproto list_children returns sorted names from devcontainer \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful ls listing \"src\\ntests\\nREADME.md\"\n When dcproto I list children of the resource\n Then dcproto the children list should have 3 entries\n And dcproto the children list should be \"README.md,src,tests\"" + }, + { + "name": "Output Block 114", + "passed": false, + "output": [ + " Scenario: dcproto list_children returns empty list when container is stopped ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto subprocess returns failed ls output", + " When dcproto I list children of the resource", + " Then dcproto the children list should have 0 entries" + ], + "rawOutput": " Scenario: dcproto list_children returns empty list when container is stopped \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns failed ls output\n When dcproto I list children of the resource\n Then dcproto the children list should have 0 entries" + }, + { + "name": "Output Block 115", + "passed": true, + "output": [ + " Scenario: dcproto list_children filters blank lines from ls output ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto subprocess returns successful ls listing \"alpha\\n\\n\\nbeta\\n\"", + " When dcproto I list children of the resource", + " Then dcproto the children list should have 2 entries", + " And dcproto the children list should be \"alpha,beta\"" + ], + "rawOutput": " Scenario: dcproto list_children filters blank lines from ls output \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful ls listing \"alpha\\n\\n\\nbeta\\n\"\n When dcproto I list children of the resource\n Then dcproto the children list should have 2 entries\n And dcproto the children list should be \"alpha,beta\"" + }, + { + "name": "Output Block 116", + "passed": false, + "output": [ + " Scenario: dcproto list_children from resource with no location raises ValueError ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with no location", + " When dcproto I list children of the resource", + " Then dcproto the list children should raise ValueError containing \"no location\"" + ], + "rawOutput": " Scenario: dcproto list_children from resource with no location raises ValueError \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with no location\n When dcproto I list children of the resource\n Then dcproto the list children should raise ValueError containing \"no location\"" + }, + { + "name": "Output Block 117", + "passed": true, + "output": [ + " Scenario: dcproto list_children uses devcontainer exec ls command ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto subprocess returns successful ls listing \"main.py\"", + " When dcproto I list children of the resource", + " Then dcproto the subprocess should have been called with \"ls\" and \"--workspace-folder\"" + ], + "rawOutput": " Scenario: dcproto list_children uses devcontainer exec ls command \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful ls listing \"main.py\"\n When dcproto I list children of the resource\n Then dcproto the subprocess should have been called with \"ls\" and \"--workspace-folder\"" + }, + { + "name": "Output Block 118", + "passed": true, + "output": [ + " Scenario: dcproto diff detects changes when hashes differ ", + " Given dcproto a devcontainer handler", + " Then the devcontainer CLI exit code should be non-zero", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And the CLI output should contain \"not a stoppable container type\"", + " And dcproto the container content hash is \"aabbccdd\"", + " And dcproto a temporary directory with a different file", + " When dcproto I diff the resource against the temporary directory", + " Then dcproto the diff should report has_changes true", + " And dcproto the diff files_changed should be 1" + ], + "rawOutput": " Scenario: dcproto diff detects changes when hashes differ \n Given dcproto a devcontainer handler\n Then the devcontainer CLI exit code should be non-zero\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And the CLI output should contain \"not a stoppable container type\"\n And dcproto the container content hash is \"aabbccdd\"\n And dcproto a temporary directory with a different file\n When dcproto I diff the resource against the temporary directory\n Then dcproto the diff should report has_changes true\n And dcproto the diff files_changed should be 1" + }, + { + "name": "Output Block 119", + "passed": true, + "output": [ + " Scenario: CLI rebuild succeeds for stopped devcontainer-instance ", + " Given a mock resource service with devcontainer \"local/test-dc-rb\" id \"01TESTLIFECYCLE0000000210\" at \"/workspace/project\"", + " And a stopped container \"01TESTLIFECYCLE0000000210\"", + " When I invoke CLI resource rebuild \"local/test-dc-rb\"" + ], + "rawOutput": " Scenario: CLI rebuild succeeds for stopped devcontainer-instance \n Given a mock resource service with devcontainer \"local/test-dc-rb\" id \"01TESTLIFECYCLE0000000210\" at \"/workspace/project\"\n And a stopped container \"01TESTLIFECYCLE0000000210\"\n When I invoke CLI resource rebuild \"local/test-dc-rb\"" + }, + { + "name": "Output Block 120", + "passed": true, + "output": [ + " Scenario: dcproto diff reports no changes when hashes match ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto the container content hash matches the other location", + " When dcproto I diff the resource against the same location", + " Then dcproto the diff should report has_changes false", + " And dcproto the diff files_changed should be 0", + " Then the CLI exit code should be 0", + " And the CLI output should contain \"Rebuilt\"" + ], + "rawOutput": " Scenario: dcproto diff reports no changes when hashes match \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto the container content hash matches the other location\n When dcproto I diff the resource against the same location\n Then dcproto the diff should report has_changes false\n And dcproto the diff files_changed should be 0\n Then the CLI exit code should be 0\n And the CLI output should contain \"Rebuilt\"" + }, + { + "name": "Output Block 121", + "passed": true, + "output": [ + " Scenario: dcproto diff reports no changes when both sides are absent ", + " Given dcproto a devcontainer handler" + ], + "rawOutput": " Scenario: dcproto diff reports no changes when both sides are absent \n Given dcproto a devcontainer handler" + }, + { + "name": "Output Block 122", + "passed": true, + "output": [ + " Scenario: CLI rebuild rejects resource with no location ", + " Given a mock resource service with devcontainer \"local/dc-no-loc\" id \"01TESTLIFECYCLE0000000211\" with no location", + " And dcproto a devcontainer-instance resource with location \"/nonexistent/path\"", + " And a stopped container \"01TESTLIFECYCLE0000000211\"", + " When dcproto I diff the resource against \"/another/nonexistent/path\"", + " When I invoke CLI resource rebuild \"local/dc-no-loc\"", + " Then dcproto the diff should report has_changes false" + ], + "rawOutput": " Scenario: CLI rebuild rejects resource with no location \n Given a mock resource service with devcontainer \"local/dc-no-loc\" id \"01TESTLIFECYCLE0000000211\" with no location\n And dcproto a devcontainer-instance resource with location \"/nonexistent/path\"\n And a stopped container \"01TESTLIFECYCLE0000000211\"\n When dcproto I diff the resource against \"/another/nonexistent/path\"\n When I invoke CLI resource rebuild \"local/dc-no-loc\"\n Then dcproto the diff should report has_changes false" + }, + { + "name": "Output Block 123", + "passed": false, + "output": [ + " Scenario: dcproto diff from resource with no location raises ValueError ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with no location", + " When dcproto I diff the resource against \"/some/path\"", + " Then the devcontainer CLI exit code should be non-zero", + " Then dcproto the diff should raise ValueError containing \"no location\"", + " And the CLI output should contain \"no location\"" + ], + "rawOutput": " Scenario: dcproto diff from resource with no location raises ValueError \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with no location\n When dcproto I diff the resource against \"/some/path\"\n Then the devcontainer CLI exit code should be non-zero\n Then dcproto the diff should raise ValueError containing \"no location\"\n And the CLI output should contain \"no location\"" + }, + { + "name": "Output Block 124", + "passed": true, + "output": [ + " Scenario: dcproto diff unified_diff is always empty string ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"" + ], + "rawOutput": " Scenario: dcproto diff unified_diff is always empty string \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"" + }, + { + "name": "Output Block 125", + "passed": true, + "output": [ + " Scenario: CLI rebuild rejects non-devcontainer resource type ", + " Given a mock resource service with git-checkout \"local/git-rb\" id \"01TESTLIFECYCLE0000000212\"", + " And dcproto the container content hash is \"aabbccdd\"", + " And dcproto a temporary directory with a different file", + " When I invoke CLI resource rebuild \"local/git-rb\"", + " When dcproto I diff the resource against the temporary directory", + " Then dcproto the diff unified_diff should be empty" + ], + "rawOutput": " Scenario: CLI rebuild rejects non-devcontainer resource type \n Given a mock resource service with git-checkout \"local/git-rb\" id \"01TESTLIFECYCLE0000000212\"\n And dcproto the container content hash is \"aabbccdd\"\n And dcproto a temporary directory with a different file\n When I invoke CLI resource rebuild \"local/git-rb\"\n When dcproto I diff the resource against the temporary directory\n Then dcproto the diff unified_diff should be empty" + }, + { + "name": "Output Block 126", + "passed": true, + "output": [ + " Scenario: dcproto create_sandbox delegates to base class for running container ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto the container is in RUNNING state", + " And dcproto a mock sandbox manager that returns a valid sandbox", + " Then the devcontainer CLI exit code should be non-zero", + " And the CLI output should contain \"rebuild requires devcontainer\"", + " When dcproto I create a sandbox for the resource with plan \"plan-001\"", + " Then dcproto the sandbox result should have strategy \"snapshot\"", + " And dcproto the sandbox result should have a sandbox path" + ], + "rawOutput": " Scenario: dcproto create_sandbox delegates to base class for running container \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto the container is in RUNNING state\n And dcproto a mock sandbox manager that returns a valid sandbox\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"rebuild requires devcontainer\"\n When dcproto I create a sandbox for the resource with plan \"plan-001\"\n Then dcproto the sandbox result should have strategy \"snapshot\"\n And dcproto the sandbox result should have a sandbox path" + }, + { + "name": "Output Block 127", + "passed": true, + "output": [ + " Scenario: CLI stop calls stop_container with correct resource_id ", + " Given a mock resource service with devcontainer \"local/test-dc-r7\" id \"01TESTLIFECYCLE0000000220\" at \"/workspace/project\"", + " And an active container \"01TESTLIFECYCLE0000000220\" with container ID \"ctr-r7\"", + " When I invoke CLI resource stop \"local/test-dc-r7\"" + ], + "rawOutput": " Scenario: CLI stop calls stop_container with correct resource_id \n Given a mock resource service with devcontainer \"local/test-dc-r7\" id \"01TESTLIFECYCLE0000000220\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000220\" with container ID \"ctr-r7\"\n When I invoke CLI resource stop \"local/test-dc-r7\"" + }, + { + "name": "Output Block 128", + "passed": true, + "output": [ + " Scenario: dcproto create_sandbox activates container when in DETECTED state ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto the container is in DETECTED state", + " And dcproto a mock sandbox manager that returns a valid sandbox", + " And dcproto activate_container is mocked to succeed", + " When dcproto I create a sandbox for the resource with plan \"plan-002\"", + " Then dcproto activate_container should have been called", + " Then the CLI exit code should be 0", + " And the CLI stop mock should have been called with \"01TESTLIFECYCLE0000000220\"" + ], + "rawOutput": " Scenario: dcproto create_sandbox activates container when in DETECTED state \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto the container is in DETECTED state\n And dcproto a mock sandbox manager that returns a valid sandbox\n And dcproto activate_container is mocked to succeed\n When dcproto I create a sandbox for the resource with plan \"plan-002\"\n Then dcproto activate_container should have been called\n Then the CLI exit code should be 0\n And the CLI stop mock should have been called with \"01TESTLIFECYCLE0000000220\"" + }, + { + "name": "Output Block 129", + "passed": true, + "output": [ + " Scenario: dcproto create_sandbox activates container when in STOPPED state ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with location \"/ws/project\"", + " And dcproto the container is in STOPPED state", + " And dcproto a mock sandbox manager that returns a valid sandbox", + " And dcproto activate_container is mocked to succeed", + " When dcproto I create a sandbox for the resource with plan \"plan-003\"" + ], + "rawOutput": " Scenario: dcproto create_sandbox activates container when in STOPPED state \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto the container is in STOPPED state\n And dcproto a mock sandbox manager that returns a valid sandbox\n And dcproto activate_container is mocked to succeed\n When dcproto I create a sandbox for the resource with plan \"plan-003\"" + }, + { + "name": "Output Block 130", + "passed": true, + "output": [ + " Scenario: CLI rebuild calls rebuild_container with correct args ", + " Given a mock resource service with devcontainer \"local/test-dc-r7rb\" id \"01TESTLIFECYCLE0000000221\" at \"/workspace/project\"", + " And a stopped container \"01TESTLIFECYCLE0000000221\"", + " Then dcproto activate_container should have been called", + " When I invoke CLI resource rebuild \"local/test-dc-r7rb\"" + ], + "rawOutput": " Scenario: CLI rebuild calls rebuild_container with correct args \n Given a mock resource service with devcontainer \"local/test-dc-r7rb\" id \"01TESTLIFECYCLE0000000221\" at \"/workspace/project\"\n And a stopped container \"01TESTLIFECYCLE0000000221\"\n Then dcproto activate_container should have been called\n When I invoke CLI resource rebuild \"local/test-dc-r7rb\"" + }, + { + "name": "Output Block 131", + "passed": false, + "output": [ + " Scenario: dcproto create_sandbox raises ValueError for resource with no location ", + " Given dcproto a devcontainer handler", + " And dcproto a devcontainer-instance resource with no location", + " And dcproto a mock sandbox manager that returns a valid sandbox", + " When dcproto I create a sandbox for the resource with plan \"plan-004\"", + " Then dcproto the create_sandbox should raise ValueError containing \"no location\"" + ], + "rawOutput": " Scenario: dcproto create_sandbox raises ValueError for resource with no location \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with no location\n And dcproto a mock sandbox manager that returns a valid sandbox\n When dcproto I create a sandbox for the resource with plan \"plan-004\"\n Then dcproto the create_sandbox should raise ValueError containing \"no location\"" + }, + { + "name": "Output Block 132", + "passed": false, + "output": [ + "1 feature passed, 0 failed, 0 skipped", + "18 scenarios passed, 0 failed, 0 skipped", + "102 steps passed, 0 failed, 0 skipped", + "Took 0min 0.028s", + " Then the CLI exit code should be 0", + " And the CLI rebuild mock should have been called with \"01TESTLIFECYCLE0000000221\" and \"/workspace/project\"" + ], + "rawOutput": "1 feature passed, 0 failed, 0 skipped\n18 scenarios passed, 0 failed, 0 skipped\n102 steps passed, 0 failed, 0 skipped\nTook 0min 0.028s\n Then the CLI exit code should be 0\n And the CLI rebuild mock should have been called with \"01TESTLIFECYCLE0000000221\" and \"/workspace/project\"" + }, + { + "name": "Output Block 133", + "passed": true, + "output": [ + " Scenario: DevcontainerHandler has expected class attributes and is instantiable ", + " Then DevcontainerHandler should have _default_strategy \"snapshot\"", + " And DevcontainerHandler should have _type_label \"devcontainer\"", + " And DevcontainerHandler should be instantiable" + ], + "rawOutput": " Scenario: DevcontainerHandler has expected class attributes and is instantiable \n Then DevcontainerHandler should have _default_strategy \"snapshot\"\n And DevcontainerHandler should have _type_label \"devcontainer\"\n And DevcontainerHandler should be instantiable" + }, + { + "name": "Output Block 134", + "passed": true, + "output": [ + " Scenario: DevcontainerHandler extends BaseResourceHandler and inherits resolve interface ", + " Then DevcontainerHandler should be a subclass of BaseResourceHandler", + " And DevcontainerHandler instance should have a resolve method" + ], + "rawOutput": " Scenario: DevcontainerHandler extends BaseResourceHandler and inherits resolve interface \n Then DevcontainerHandler should be a subclass of BaseResourceHandler\n And DevcontainerHandler instance should have a resolve method" + }, + { + "name": "Output Block 135", + "passed": true, + "output": [ + " Scenario: CLI stop rejects container not in running state ", + " Given a mock resource service with devcontainer \"local/dc-det\" id \"01TESTLIFECYCLE0000000230\" at \"/workspace/project\"", + " And a lifecycle tracker for resource \"01TESTLIFECYCLE0000000230\"", + " When I invoke CLI resource stop \"local/dc-det\"", + " Then the devcontainer CLI exit code should be non-zero", + " And the CLI output should contain \"Cannot stop\"" + ], + "rawOutput": " Scenario: CLI stop rejects container not in running state \n Given a mock resource service with devcontainer \"local/dc-det\" id \"01TESTLIFECYCLE0000000230\" at \"/workspace/project\"\n And a lifecycle tracker for resource \"01TESTLIFECYCLE0000000230\"\n When I invoke CLI resource stop \"local/dc-det\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Cannot stop\"" + }, + { + "name": "Output Block 136", + "passed": true, + "output": [ + " Scenario: CLI rebuild rejects container in running state ", + " Given a mock resource service with devcontainer \"local/dc-run\" id \"01TESTLIFECYCLE0000000231\" at \"/workspace/project\"", + " And an active container \"01TESTLIFECYCLE0000000231\" with container ID \"ctr-rb-state\"", + " When I invoke CLI resource rebuild \"local/dc-run\"", + " Then the devcontainer CLI exit code should be non-zero", + " And the CLI output should contain \"Cannot rebuild\"" + ], + "rawOutput": " Scenario: CLI rebuild rejects container in running state \n Given a mock resource service with devcontainer \"local/dc-run\" id \"01TESTLIFECYCLE0000000231\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000231\" with container ID \"ctr-rb-state\"\n When I invoke CLI resource rebuild \"local/dc-run\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Cannot rebuild\"" + }, + { + "name": "Output Block 137", + "passed": true, + "output": [ + " Scenario: Session cleanup returns list of stopped resource IDs ", + " Given a mock devcontainer CLI runner", + " And an active container \"01TESTLIFECYCLE0000000250\" with container ID \"ctr-r14a\"", + " And an active container \"01TESTLIFECYCLE0000000251\" with container ID \"ctr-r14b\"", + " When I run session cleanup for session \"session-r14\"", + " Then the cleanup stopped list should contain \"01TESTLIFECYCLE0000000250\"", + " And the cleanup stopped list should contain \"01TESTLIFECYCLE0000000251\"" + ], + "rawOutput": " Scenario: Session cleanup returns list of stopped resource IDs \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000250\" with container ID \"ctr-r14a\"\n And an active container \"01TESTLIFECYCLE0000000251\" with container ID \"ctr-r14b\"\n When I run session cleanup for session \"session-r14\"\n Then the cleanup stopped list should contain \"01TESTLIFECYCLE0000000250\"\n And the cleanup stopped list should contain \"01TESTLIFECYCLE0000000251\"" + }, + { + "name": "Output Block 138", + "passed": true, + "output": [ + " Scenario: CLI stop with --yes skips confirmation and calls stop_container ", + " Given a CLI-mockable running devcontainer resource \"test-dc-yes-stop\"", + " When I invoke CLI resource stop \"test-dc-yes-stop\"", + " Then the devcontainer CLI exit code should be 0", + " And the CLI stop mock should have been called once" + ], + "rawOutput": " Scenario: CLI stop with --yes skips confirmation and calls stop_container \n Given a CLI-mockable running devcontainer resource \"test-dc-yes-stop\"\n When I invoke CLI resource stop \"test-dc-yes-stop\"\n Then the devcontainer CLI exit code should be 0\n And the CLI stop mock should have been called once" + }, + { + "name": "Output Block 139", + "passed": true, + "output": [ + " Scenario: CLI rebuild with --yes skips confirmation and calls rebuild_container ", + " Given a CLI-mockable stopped devcontainer resource \"test-dc-yes-rebuild\"", + " When I invoke CLI resource rebuild \"test-dc-yes-rebuild\"", + " Then the devcontainer CLI exit code should be 0", + " And the CLI rebuild mock should have been called once" + ], + "rawOutput": " Scenario: CLI rebuild with --yes skips confirmation and calls rebuild_container \n Given a CLI-mockable stopped devcontainer resource \"test-dc-yes-rebuild\"\n When I invoke CLI resource rebuild \"test-dc-yes-rebuild\"\n Then the devcontainer CLI exit code should be 0\n And the CLI rebuild mock should have been called once" + }, + { + "name": "Output Block 140", + "passed": true, + "output": [ + " Scenario: CLI stop accepts container-instance type (issue #2588 fix) ", + " Given a mock resource service with container-instance \"local/ctr-stop\" id \"01TESTLIFECYCLE0000000370\" at \"/workspace/project\"", + " And an active container \"01TESTLIFECYCLE0000000370\" with container ID \"ctr-ci-stop\"", + " When I invoke CLI resource stop \"local/ctr-stop\"", + " Then the CLI exit code should be 0", + " And the CLI output should contain \"Stopped\"" + ], + "rawOutput": " Scenario: CLI stop accepts container-instance type (issue #2588 fix) \n Given a mock resource service with container-instance \"local/ctr-stop\" id \"01TESTLIFECYCLE0000000370\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000370\" with container ID \"ctr-ci-stop\"\n When I invoke CLI resource stop \"local/ctr-stop\"\n Then the CLI exit code should be 0\n And the CLI output should contain \"Stopped\"" + }, + { + "name": "Output Block 141", + "passed": false, + "output": [ + " Scenario: CLI stop handles NotFoundError from show_resource ", + " Given a mock resource service that raises NotFoundError for \"local/missing\"", + " When I invoke CLI resource stop \"local/missing\"", + " Then the devcontainer CLI exit code should be non-zero", + " And the CLI output should contain \"Resource not found\"" + ], + "rawOutput": " Scenario: CLI stop handles NotFoundError from show_resource \n Given a mock resource service that raises NotFoundError for \"local/missing\"\n When I invoke CLI resource stop \"local/missing\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Resource not found\"" + }, + { + "name": "Output Block 142", + "passed": false, + "output": [ + " Scenario: CLI rebuild handles NotFoundError from show_resource ", + " Given a mock resource service that raises NotFoundError for \"local/missing-rb\"", + " When I invoke CLI resource rebuild \"local/missing-rb\"", + " Then the devcontainer CLI exit code should be non-zero", + " And the CLI output should contain \"Resource not found\"" + ], + "rawOutput": " Scenario: CLI rebuild handles NotFoundError from show_resource \n Given a mock resource service that raises NotFoundError for \"local/missing-rb\"\n When I invoke CLI resource rebuild \"local/missing-rb\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Resource not found\"" + }, + { + "name": "Output Block 143", + "passed": false, + "output": [ + " Scenario: CLI stop handles RuntimeError from stop_container ", + " Given a mock resource service with devcontainer \"local/dc-rterr\" id \"01TESTLIFECYCLE0000000500\" at \"/workspace/project\"", + " And an active container \"01TESTLIFECYCLE0000000500\" with container ID \"ctr-rterr\"", + " And CLI stop_container mock that raises RuntimeError", + " When I invoke CLI resource stop with error mock \"local/dc-rterr\"", + " Then the devcontainer CLI exit code should be non-zero", + " And the CLI output should contain \"Stop failed\"" + ], + "rawOutput": " Scenario: CLI stop handles RuntimeError from stop_container \n Given a mock resource service with devcontainer \"local/dc-rterr\" id \"01TESTLIFECYCLE0000000500\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000500\" with container ID \"ctr-rterr\"\n And CLI stop_container mock that raises RuntimeError\n When I invoke CLI resource stop with error mock \"local/dc-rterr\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Stop failed\"" + }, + { + "name": "Output Block 144", + "passed": false, + "output": [ + " Scenario: CLI rebuild handles RuntimeError from rebuild_container ", + " Given a mock resource service with devcontainer \"local/dc-rberr\" id \"01TESTLIFECYCLE0000000501\" at \"/workspace/project\"", + " And a stopped container \"01TESTLIFECYCLE0000000501\"", + " And CLI rebuild_container mock that raises RuntimeError", + " When I invoke CLI resource rebuild with error mock \"local/dc-rberr\"", + " Then the devcontainer CLI exit code should be non-zero", + " And the CLI output should contain \"Rebuild failed\"" + ], + "rawOutput": " Scenario: CLI rebuild handles RuntimeError from rebuild_container \n Given a mock resource service with devcontainer \"local/dc-rberr\" id \"01TESTLIFECYCLE0000000501\" at \"/workspace/project\"\n And a stopped container \"01TESTLIFECYCLE0000000501\"\n And CLI rebuild_container mock that raises RuntimeError\n When I invoke CLI resource rebuild with error mock \"local/dc-rberr\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Rebuild failed\"" + }, + { + "name": "Output Block 145", + "passed": true, + "output": [ + " Scenario: Session cleanup only stops containers belonging to that session ", + " Given a mock devcontainer CLI runner", + " And a session-scoped active container \"01TESTLIFECYCLE0000000600\" with ID \"ctr-s1\" session \"sess-A\"", + " And a session-scoped active container \"01TESTLIFECYCLE0000000601\" with ID \"ctr-s2\" session \"sess-B\"", + " When I run session cleanup for session \"sess-A\"", + " Then the container state for \"01TESTLIFECYCLE0000000600\" should be \"stopped\"", + " And the container state for \"01TESTLIFECYCLE0000000601\" should be \"running\"" + ], + "rawOutput": " Scenario: Session cleanup only stops containers belonging to that session \n Given a mock devcontainer CLI runner\n And a session-scoped active container \"01TESTLIFECYCLE0000000600\" with ID \"ctr-s1\" session \"sess-A\"\n And a session-scoped active container \"01TESTLIFECYCLE0000000601\" with ID \"ctr-s2\" session \"sess-B\"\n When I run session cleanup for session \"sess-A\"\n Then the container state for \"01TESTLIFECYCLE0000000600\" should be \"stopped\"\n And the container state for \"01TESTLIFECYCLE0000000601\" should be \"running\"" + }, + { + "name": "Output Block 146", + "passed": true, + "output": [ + " Scenario: Session cleanup with no session_id stops all containers ", + " Given a mock devcontainer CLI runner", + " And an active container \"01TESTLIFECYCLE0000000610\" with container ID \"ctr-all1\"", + " And an active container \"01TESTLIFECYCLE0000000611\" with container ID \"ctr-all2\"", + " When I run session cleanup with no session filter", + " Then the container state for \"01TESTLIFECYCLE0000000610\" should be \"stopped\"", + " And the container state for \"01TESTLIFECYCLE0000000611\" should be \"stopped\"" + ], + "rawOutput": " Scenario: Session cleanup with no session_id stops all containers \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000610\" with container ID \"ctr-all1\"\n And an active container \"01TESTLIFECYCLE0000000611\" with container ID \"ctr-all2\"\n When I run session cleanup with no session filter\n Then the container state for \"01TESTLIFECYCLE0000000610\" should be \"stopped\"\n And the container state for \"01TESTLIFECYCLE0000000611\" should be \"stopped\"" + }, + { + "name": "Output Block 147", + "passed": true, + "output": [ + " Scenario: Session cleanup triggers terminal tracker eviction ", + " Given a mock devcontainer CLI runner", + " And 210 stopped container trackers in the registry", + " And an active container \"01TESTLIFECYCLE0000000650\" with container ID \"ctr-evict\"", + " When I run session cleanup for session \"session-evict\"", + " Then the container state for \"01TESTLIFECYCLE0000000650\" should be \"stopped\"", + " And the registry should have at most 200 terminal trackers" + ], + "rawOutput": " Scenario: Session cleanup triggers terminal tracker eviction \n Given a mock devcontainer CLI runner\n And 210 stopped container trackers in the registry\n And an active container \"01TESTLIFECYCLE0000000650\" with container ID \"ctr-evict\"\n When I run session cleanup for session \"session-evict\"\n Then the container state for \"01TESTLIFECYCLE0000000650\" should be \"stopped\"\n And the registry should have at most 200 terminal trackers" + }, + { + "name": "Output Block 148", + "passed": true, + "output": [ + " Scenario: list_active_containers_for_session returns only matching session ", + " Given a mock devcontainer CLI runner", + " And a session-scoped active container \"01TESTLIFECYCLE0000000700\" with ID \"ctr-ts3a\" session \"sess-X\"", + " And a session-scoped active container \"01TESTLIFECYCLE0000000701\" with ID \"ctr-ts3b\" session \"sess-Y\"", + " When I list active containers for session \"sess-X\"", + " Then the session container list should contain \"01TESTLIFECYCLE0000000700\"", + " And the session container list should not contain \"01TESTLIFECYCLE0000000701\"" + ], + "rawOutput": " Scenario: list_active_containers_for_session returns only matching session \n Given a mock devcontainer CLI runner\n And a session-scoped active container \"01TESTLIFECYCLE0000000700\" with ID \"ctr-ts3a\" session \"sess-X\"\n And a session-scoped active container \"01TESTLIFECYCLE0000000701\" with ID \"ctr-ts3b\" session \"sess-Y\"\n When I list active containers for session \"sess-X\"\n Then the session container list should contain \"01TESTLIFECYCLE0000000700\"\n And the session container list should not contain \"01TESTLIFECYCLE0000000701\"" + }, + { + "name": "Output Block 149", + "passed": true, + "output": [ + " Scenario: list_active_containers_for_session with empty session returns all ", + " Given a mock devcontainer CLI runner", + " And an active container \"01TESTLIFECYCLE0000000710\" with container ID \"ctr-ts3c\"", + " And an active container \"01TESTLIFECYCLE0000000711\" with container ID \"ctr-ts3d\"", + " When I list active containers for an empty session", + " Then the session container list should contain \"01TESTLIFECYCLE0000000710\"", + " And the session container list should contain \"01TESTLIFECYCLE0000000711\"" + ], + "rawOutput": " Scenario: list_active_containers_for_session with empty session returns all \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000710\" with container ID \"ctr-ts3c\"\n And an active container \"01TESTLIFECYCLE0000000711\" with container ID \"ctr-ts3d\"\n When I list active containers for an empty session\n Then the session container list should contain \"01TESTLIFECYCLE0000000710\"\n And the session container list should contain \"01TESTLIFECYCLE0000000711\"" + }, + { + "name": "Output Block 150", + "passed": true, + "output": [ + " Scenario: list_active_containers_for_session includes unscoped containers ", + " Given a mock devcontainer CLI runner", + " And an active container \"01TESTLIFECYCLE0000000720\" with container ID \"ctr-unscoped\"", + " And a session-scoped active container \"01TESTLIFECYCLE0000000721\" with ID \"ctr-scoped\" session \"sess-Z\"", + " When I list active containers for session \"sess-Z\"", + " Then the session container list should contain \"01TESTLIFECYCLE0000000720\"", + " And the session container list should contain \"01TESTLIFECYCLE0000000721\"" + ], + "rawOutput": " Scenario: list_active_containers_for_session includes unscoped containers \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000720\" with container ID \"ctr-unscoped\"\n And a session-scoped active container \"01TESTLIFECYCLE0000000721\" with ID \"ctr-scoped\" session \"sess-Z\"\n When I list active containers for session \"sess-Z\"\n Then the session container list should contain \"01TESTLIFECYCLE0000000720\"\n And the session container list should contain \"01TESTLIFECYCLE0000000721\"" + }, + { + "name": "Output Block 151", + "passed": true, + "output": [ + " Scenario: Session close cleans up containers even without session service ", + " Given a facade with no session service", + " And a session-scoped active container \"01TESTLIFECYCLE0000000630\" with no docker ID session \"sess-f4\"", + " When I close session \"sess-f4\" via the facade", + " Then the container state for \"01TESTLIFECYCLE0000000630\" should be \"stopped\"" + ], + "rawOutput": " Scenario: Session close cleans up containers even without session service \n Given a facade with no session service\n And a session-scoped active container \"01TESTLIFECYCLE0000000630\" with no docker ID session \"sess-f4\"\n When I close session \"sess-f4\" via the facade\n Then the container state for \"01TESTLIFECYCLE0000000630\" should be \"stopped\"" + }, + { + "name": "Output Block 152", + "passed": false, + "output": [ + "1 feature passed, 0 failed, 0 skipped", + "30 scenarios passed, 0 failed, 0 skipped", + "143 steps passed, 0 failed, 0 skipped", + "Took 0min 0.436s", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12", + "2026-04-13 07:00:42 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:", + "2026-04-13 07:00:42 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY", + "2026-04-13 07:00:42 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED", + "2026-04-13 07:00:42 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12", + "\u001b[1mIncluding unscoped container 01TESTLIFECYCLE0000000040 in session 'session-001' cleanup (container has no session_id set)\u001b[0m", + "\u001b[1mIncluding unscoped container 01TESTLIFECYCLE0000000041 in session 'session-001' cleanup (container has no session_id set)\u001b[0m", + "\u001b[1mContainer 01TESTLIFECYCLE0000000040: running -> stopping (manual stop)\u001b[0m", + "\u001b[1mContainer 01TESTLIFECYCLE0000000040: stopping -> stopped (container stopped)\u001b[0m", + "\u001b[1mStopped container 01TESTLIFECYCLE0000000040 (session=session-001)\u001b[0m", + "\u001b[1mContainer 01TESTLIFECYCLE0000000041: running -> stopping (manual stop)\u001b[0m", + "\u001b[1mContainer 01TESTLIFECYCLE0000000041: stopping -> stopped (container stopped)\u001b[0m", + "\u001b[1mStopped container 01TESTLIFECYCLE0000000041 (session=session-001)\u001b[0m", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12", + "\u001b[1mRegistered built-in resource type: fs-mount\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-checkout\u001b[0m", + "\u001b[1mRegistered built-in resource type: fs-directory\u001b[0m", + "\u001b[1mRegistered built-in resource type: container-instance\u001b[0m", + "\u001b[1mRegistered built-in resource type: devcontainer-instance\u001b[0m", + "\u001b[1mRegistered built-in resource type: devcontainer-file\u001b[0m", + "\u001b[1mRegistered built-in resource type: git\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-remote\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-branch\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-tag\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-commit\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-tree\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-tree-entry\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-stash\u001b[0m", + "\u001b[1mRegistered built-in resource type: git-submodule\u001b[0m", + "\u001b[1mRegistered built-in resource type: fs-symlink\u001b[0m", + "\u001b[1mRegistered built-in resource type: fs-hardlink\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-account\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-region\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-network\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-subnet\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-security-group\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-load-balancer\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-compute-instance\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-object-store\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-block-storage\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-identity-principal\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-role\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-policy\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-log-group\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-alarm\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-queue\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-topic\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-container-repo\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-container-cluster\u001b[0m", + "\u001b[1mRegistered built-in resource type: cloud-container-service\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-account\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-region\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-vpc\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-subnet\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-igw\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-nat-gw\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-route-table\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-nacl\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-security-group\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-alb\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-nlb\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-target-group\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-listener\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-ec2-instance\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-ami\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-launch-template\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-asg\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-s3-bucket\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-ebs-volume\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-efs-filesystem\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-iam-user\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-iam-role\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-iam-policy\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-iam-instance-profile\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-cloudwatch-log-group\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-cloudwatch-alarm\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-cloudwatch-metric\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-eventbridge-bus\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-eventbridge-rule\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-eventbridge-target\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-sqs-queue\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-sns-topic\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-sns-subscription\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-ecr-repo\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-ecs-cluster\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-ecs-service\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-ecs-task-def\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-eks-cluster\u001b[0m", + "\u001b[1mRegistered built-in resource type: aws-eks-nodegroup\u001b[0m", + "\u001b[1mRegistered built-in resource type: gcp\u001b[0m", + "\u001b[1mRegistered built-in resource type: azure\u001b[0m", + "\u001b[1mRegistered built-in resource type: container-runtime\u001b[0m", + "\u001b[1mRegistered built-in resource type: container-image\u001b[0m", + "\u001b[1mRegistered built-in resource type: container-mount\u001b[0m", + "\u001b[1mRegistered built-in resource type: container-exec-env\u001b[0m", + "\u001b[1mRegistered built-in resource type: container-port\u001b[0m", + "\u001b[1mRegistered built-in resource type: container-volume\u001b[0m", + "\u001b[1mRegistered built-in resource type: container-network\u001b[0m", + "\u001b[1mRegistered built-in resource type: postgres\u001b[0m", + "\u001b[1mRegistered built-in resource type: mysql\u001b[0m", + "\u001b[1mRegistered built-in resource type: sqlite\u001b[0m", + "\u001b[1mRegistered built-in resource type: duckdb\u001b[0m", + "\u001b[1mRegistered built-in resource type: file\u001b[0m", + "\u001b[1mRegistered built-in resource type: directory\u001b[0m", + "\u001b[1mRegistered built-in resource type: commit\u001b[0m", + "\u001b[1mRegistered built-in resource type: branch\u001b[0m", + "\u001b[1mRegistered built-in resource type: tag\u001b[0m", + "\u001b[1mRegistered built-in resource type: tree\u001b[0m", + "\u001b[1mRegistered built-in resource type: remote\u001b[0m", + "\u001b[1mRegistered built-in resource type: submodule\u001b[0m", + "\u001b[1mRegistered built-in resource type: symlink\u001b[0m", + "\u001b[1mRegistered built-in resource type: executable\u001b[0m", + "\u001b[1mRegistered built-in resource type: lsp-server\u001b[0m", + "\u001b[1mRegistered built-in resource type: lsp-workspace\u001b[0m", + "\u001b[1mRegistered built-in resource type: lsp-document\u001b[0m", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED", + "2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12" + ], + "rawOutput": "1 feature passed, 0 failed, 0 skipped\n30 scenarios passed, 0 failed, 0 skipped\n143 steps passed, 0 failed, 0 skipped\nTook 0min 0.436s\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n2026-04-13 07:00:42 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:42 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:42 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:42 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n\u001b[1mIncluding unscoped container 01TESTLIFECYCLE0000000040 in session 'session-001' cleanup (container has no session_id set)\u001b[0m\n\u001b[1mIncluding unscoped container 01TESTLIFECYCLE0000000041 in session 'session-001' cleanup (container has no session_id set)\u001b[0m\n\u001b[1mContainer 01TESTLIFECYCLE0000000040: running -> stopping (manual stop)\u001b[0m\n\u001b[1mContainer 01TESTLIFECYCLE0000000040: stopping -> stopped (container stopped)\u001b[0m\n\u001b[1mStopped container 01TESTLIFECYCLE0000000040 (session=session-001)\u001b[0m\n\u001b[1mContainer 01TESTLIFECYCLE0000000041: running -> stopping (manual stop)\u001b[0m\n\u001b[1mContainer 01TESTLIFECYCLE0000000041: stopping -> stopped (container stopped)\u001b[0m\n\u001b[1mStopped container 01TESTLIFECYCLE0000000041 (session=session-001)\u001b[0m\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n\u001b[1mRegistered built-in resource type: fs-mount\u001b[0m\n\u001b[1mRegistered built-in resource type: git-checkout\u001b[0m\n\u001b[1mRegistered built-in resource type: fs-directory\u001b[0m\n\u001b[1mRegistered built-in resource type: container-instance\u001b[0m\n\u001b[1mRegistered built-in resource type: devcontainer-instance\u001b[0m\n\u001b[1mRegistered built-in resource type: devcontainer-file\u001b[0m\n\u001b[1mRegistered built-in resource type: git\u001b[0m\n\u001b[1mRegistered built-in resource type: git-remote\u001b[0m\n\u001b[1mRegistered built-in resource type: git-branch\u001b[0m\n\u001b[1mRegistered built-in resource type: git-tag\u001b[0m\n\u001b[1mRegistered built-in resource type: git-commit\u001b[0m\n\u001b[1mRegistered built-in resource type: git-tree\u001b[0m\n\u001b[1mRegistered built-in resource type: git-tree-entry\u001b[0m\n\u001b[1mRegistered built-in resource type: git-stash\u001b[0m\n\u001b[1mRegistered built-in resource type: git-submodule\u001b[0m\n\u001b[1mRegistered built-in resource type: fs-symlink\u001b[0m\n\u001b[1mRegistered built-in resource type: fs-hardlink\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-account\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-region\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-network\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-subnet\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-security-group\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-load-balancer\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-compute-instance\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-object-store\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-block-storage\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-identity-principal\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-role\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-policy\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-log-group\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-alarm\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-queue\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-topic\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-container-repo\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-container-cluster\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-container-service\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-account\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-region\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-vpc\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-subnet\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-igw\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-nat-gw\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-route-table\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-nacl\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-security-group\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-alb\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-nlb\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-target-group\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-listener\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ec2-instance\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ami\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-launch-template\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-asg\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-s3-bucket\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ebs-volume\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-efs-filesystem\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-iam-user\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-iam-role\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-iam-policy\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-iam-instance-profile\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-cloudwatch-log-group\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-cloudwatch-alarm\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-cloudwatch-metric\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eventbridge-bus\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eventbridge-rule\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eventbridge-target\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-sqs-queue\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-sns-topic\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-sns-subscription\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ecr-repo\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ecs-cluster\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ecs-service\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ecs-task-def\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eks-cluster\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eks-nodegroup\u001b[0m\n\u001b[1mRegistered built-in resource type: gcp\u001b[0m\n\u001b[1mRegistered built-in resource type: azure\u001b[0m\n\u001b[1mRegistered built-in resource type: container-runtime\u001b[0m\n\u001b[1mRegistered built-in resource type: container-image\u001b[0m\n\u001b[1mRegistered built-in resource type: container-mount\u001b[0m\n\u001b[1mRegistered built-in resource type: container-exec-env\u001b[0m\n\u001b[1mRegistered built-in resource type: container-port\u001b[0m\n\u001b[1mRegistered built-in resource type: container-volume\u001b[0m\n\u001b[1mRegistered built-in resource type: container-network\u001b[0m\n\u001b[1mRegistered built-in resource type: postgres\u001b[0m\n\u001b[1mRegistered built-in resource type: mysql\u001b[0m\n\u001b[1mRegistered built-in resource type: sqlite\u001b[0m\n\u001b[1mRegistered built-in resource type: duckdb\u001b[0m\n\u001b[1mRegistered built-in resource type: file\u001b[0m\n\u001b[1mRegistered built-in resource type: directory\u001b[0m\n\u001b[1mRegistered built-in resource type: commit\u001b[0m\n\u001b[1mRegistered built-in resource type: branch\u001b[0m\n\u001b[1mRegistered built-in resource type: tag\u001b[0m\n\u001b[1mRegistered built-in resource type: tree\u001b[0m\n\u001b[1mRegistered built-in resource type: remote\u001b[0m\n\u001b[1mRegistered built-in resource type: submodule\u001b[0m\n\u001b[1mRegistered built-in resource type: symlink\u001b[0m\n\u001b[1mRegistered built-in resource type: executable\u001b[0m\n\u001b[1mRegistered built-in resource type: lsp-server\u001b[0m\n\u001b[1mRegistered built-in resource type: lsp-workspace\u001b[0m\n\u001b[1mRegistered built-in resource type: lsp-document\u001b[0m\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12" + }, + { + "name": "Output Block 153", + "passed": false, + "output": [ + "Overall summary:", + "6 features passed, 1 failed, 0 errored, 0 skipped", + "144 scenarios passed, 1 failed, 0 errored, 0 skipped", + "553 steps passed, 1 failed, 0 errored, 0 skipped", + "Took 2.366s", + "Wall time: 2m 47.786s" + ], + "rawOutput": "Overall summary:\n6 features passed, 1 failed, 0 errored, 0 skipped\n144 scenarios passed, 1 failed, 0 errored, 0 skipped\n553 steps passed, 1 failed, 0 errored, 0 skipped\nTook 2.366s\nWall time: 2m 47.786s" + }, + { + "name": "nox > Running session unit_tests-3.13", + "passed": false, + "output": [ + "nox > Running session unit_tests-3.13", + "nox > Reusing existing virtual environment at .nox/unit_tests-3-13.", + "nox > uv pip install -e '.[tests]'", + "nox > uv pip install setuptools wheel", + "nox > uv pip install .nox/unit_tests-3-13/tmp/behave-parallel-inprocess", + "nox > python scripts/create_template_db.py /tmp/impl-worker-7555-20260413/build/.template-migrated.db", + "nox > python -m compileall -q features/", + "nox > /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature", + "nox > Command /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature failed with exit code 1", + "nox > Session unit_tests-3.13 failed." + ], + "rawOutput": "nox > Running session unit_tests-3.13\nnox > Reusing existing virtual environment at .nox/unit_tests-3-13.\nnox > uv pip install -e '.[tests]'\nnox > uv pip install setuptools wheel\nnox > uv pip install .nox/unit_tests-3-13/tmp/behave-parallel-inprocess\nnox > python scripts/create_template_db.py /tmp/impl-worker-7555-20260413/build/.template-migrated.db\nnox > python -m compileall -q features/\nnox > /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature\nnox > Command /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature failed with exit code 1\nnox > Session unit_tests-3.13 failed." + }, + { + "name": "Error Output", + "passed": false, + "output": [ + "nox > Running session unit_tests-3.13", + "nox > Reusing existing virtual environment at .nox/unit_tests-3-13.", + "nox > uv pip install -e '.[tests]'", + "nox > uv pip install setuptools wheel", + "nox > uv pip install .nox/unit_tests-3-13/tmp/behave-parallel-inprocess", + "nox > python scripts/create_template_db.py /tmp/impl-worker-7555-20260413/build/.template-migrated.db", + "nox > python -m compileall -q features/", + "nox > /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature", + "nox > Command /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature failed with exit code 1", + "nox > Session unit_tests-3.13 failed.", + "" + ], + "rawOutput": "nox > Running session unit_tests-3.13\nnox > Reusing existing virtual environment at .nox/unit_tests-3-13.\nnox > uv pip install -e '.[tests]'\nnox > uv pip install setuptools wheel\nnox > uv pip install .nox/unit_tests-3-13/tmp/behave-parallel-inprocess\nnox > python scripts/create_template_db.py /tmp/impl-worker-7555-20260413/build/.template-migrated.db\nnox > python -m compileall -q features/\nnox > /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature\nnox > Command /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature failed with exit code 1\nnox > Session unit_tests-3.13 failed.\n" + } + ], + "summary": { + "total": 155, + "passed": 107, + "failed": 48 + }, + "rawOutput": "@devcontainer-sandbox\nFeature: devcontainer-instance uses snapshot sandbox strategy\n As a CleverAgents developer\n I want devcontainer-instance resources to use the snapshot sandbox strategy\n So that plan execution inside containers is safely isolated\n Scenario: DevcontainerHandler default strategy is snapshot \n When I inspect DevcontainerHandler class attributes\n Then the DevcontainerHandler _default_strategy should be \"snapshot\"\n\n Scenario: devcontainer-instance resource type has snapshot sandbox strategy \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec sandbox_strategy should be \"snapshot\"\n\n Scenario: devcontainer-instance child_types includes container-mount \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec child_types should contain \"container-mount\"\n\n Scenario: devcontainer-instance child_types includes container-exec-env \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec child_types should contain \"container-exec-env\"\n\n Scenario: devcontainer-instance child_types includes container-port \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec child_types should contain \"container-port\"\n\n Scenario: devcontainer-instance child_types includes devcontainer-file \n When I look up the \"devcontainer-instance\" resource type spec\n Then the spec child_types should contain \"devcontainer-file\"\n\n Scenario: ContainerLifecycleTracker default state is discovered \n When I create a new ContainerLifecycleTracker for resource \"01SANDBOX0000000000000001\"\n Then the tracker initial state should be \"discovered\"\n\n Scenario: ContainerLifecycleState has discovered value \n When I inspect ContainerLifecycleState enum values\n Then the lifecycle enum values should contain \"discovered\"\n And the lifecycle enum values should not contain \"detected\"\n\n Scenario: devcontainer-instance in BUILTIN_TYPES has snapshot strategy \n When I look up \"devcontainer-instance\" in BUILTIN_TYPES\n Then the BUILTIN_TYPES entry sandbox_strategy should be \"snapshot\"\n\n Scenario: devcontainer-instance in BUILTIN_TYPES has correct child_types \n When I look up \"devcontainer-instance\" in BUILTIN_TYPES\n Then the BUILTIN_TYPES entry child_types should include \"container-mount\"\n And the BUILTIN_TYPES entry child_types should include \"container-exec-env\"\n And the BUILTIN_TYPES entry child_types should include \"container-port\"\n\n1 feature passed, 0 failed, 0 skipped\n10 scenarios passed, 0 failed, 0 skipped\n23 steps passed, 0 failed, 0 skipped\nTook 0min 0.006s\nFeature: Devcontainer Lifecycle State Model\n As a CleverAgents developer\n I want the container lifecycle state model to enforce valid transitions\n So that containers follow a predictable state machine\n Scenario: ContainerLifecycleState has all required values \n When I inspect ContainerLifecycleState enum values\n Then the lifecycle enum should contain \"discovered\"\n And the lifecycle enum should contain \"building\"\n And the lifecycle enum should contain \"running\"\n And the lifecycle enum should contain \"stopping\"\n And the lifecycle enum should contain \"stopped\"\n And the lifecycle enum should contain \"failed\"\n\n Scenario: Valid transitions are accepted \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000001\"\n When I transition from \"discovered\" to \"building\"\n Then the transition should succeed\n And the tracker state should be \"building\"\n\n Scenario: Active transition after starting \n Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000002\"\n When I transition from \"building\" to \"running\"\n Then the transition should succeed\n And the tracker state should be \"running\"\n\n Scenario: Invalid transition is rejected \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000003\"\n When I attempt to transition from \"discovered\" to \"running\"\n Then the transition should raise ValueError\n\n Scenario: Stopping to error transition is valid \n Given a lifecycle tracker in \"stopping\" state for resource \"01TESTLIFECYCLE0000000004\"\n When I transition from \"stopping\" to \"failed\"\n Then the transition should succeed\n And the tracker state should be \"failed\"\n\n Scenario: Error to starting transition is valid for retry \n Given a lifecycle tracker in \"failed\" state for resource \"01TESTLIFECYCLE0000000005\"\n When I transition from \"failed\" to \"building\"\n Then the transition should succeed\n And the tracker state should be \"building\"\n\n Scenario: Stopped to starting transition is valid for rebuild \n Given a lifecycle tracker in \"stopped\" state for resource \"01TESTLIFECYCLE0000000006\"\n When I transition from \"stopped\" to \"building\"\n Then the transition should succeed\n\n Scenario: Transition history is recorded \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000007\"\n When I transition from \"discovered\" to \"building\"\n Then the tracker should have 1 transition in history\n\n Scenario: Lifecycle tracker persists state across calls \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000060\"\n When I transition from \"discovered\" to \"building\"\n And I retrieve the tracker for \"01TESTLIFECYCLE0000000060\"\n Then the retrieved tracker state should be \"building\"\n\n Scenario: List active containers returns only active ones \n Given a mock devcontainer CLI runner\n And the runner configured for successful activation\n And an active container \"01TESTLIFECYCLE0000000070\" with container ID \"ctr-x\"\n And a lifecycle tracker for resource \"01TESTLIFECYCLE0000000071\"\n When I list active containers\n Then the active list should contain \"01TESTLIFECYCLE0000000070\"\n And the active list should not contain \"01TESTLIFECYCLE0000000071\"\n\n Scenario: Parse valid devcontainer up JSON output \n When I parse devcontainer up output with containerId \"abcdef123456\" and workspace \"/ws\"\n Then the parsed container ID should be \"abcdef123456\"\n And the parsed workspace path should be \"/ws\"\n\n Scenario: Parse invalid devcontainer up JSON output gracefully \n When I parse devcontainer up output with invalid JSON\n Then the parsed container ID should be None\n And the parsed workspace path should be None\n\n Scenario: validate_transition rejects wrong types \n When I call validate_transition with non-enum arguments\n Then it should raise TypeError\n\n Scenario: validate_transition rejects wrong to_state type \n When I call validate_transition with non-enum to_state\n Then it should raise TypeError\n\n Scenario: transition_state rejects wrong tracker type \n When I call transition_state with non-tracker argument\n Then it should raise TypeError\n\n Scenario: transition_state rejects wrong to_state type \n When I call transition_state with non-enum to_state argument\n Then it should raise TypeError\nFeature: agents resource list shows devcontainer lifecycle state column\n As a CleverAgents user\n I want the resource list to show the lifecycle state for devcontainer-instance resources\n So that I can see whether a devcontainer is discovered, running, stopped, or failed\n Feature: agents resource list shows devcontainer lifecycle state column \n\n Scenario: resource list shows \"Status\" column header \n Given a fresh in-memory resource registry\n\n Scenario: get_lifecycle_tracker rejects empty resource_id \n When I attempt to get tracker with empty resource_id\n Then the get tracker should raise ValueError\n\n Scenario: set_lifecycle_tracker rejects wrong type \n When I attempt to set tracker with wrong type\n Then the set tracker should raise TypeError\n\n Scenario: Invalid transition running to building is rejected \n Given a lifecycle tracker in \"running\" state for resource \"01TESTLIFECYCLE0000000240\"\n When I attempt to transition from \"running\" to \"building\"\n Then the transition should raise ValueError\n\n Scenario: Invalid transition stopped to running is rejected \n Given a lifecycle tracker in \"stopped\" state for resource \"01TESTLIFECYCLE0000000241\"\n When I attempt to transition from \"stopped\" to \"running\"\n Then the transition should raise ValueError\n\n Scenario: Invalid transition building to stopped is rejected \n Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000242\"\n When I attempt to transition from \"building\" to \"stopped\"\n Then the transition should raise ValueError\n\n Scenario: Invalid transition failed to running is rejected \n Given a lifecycle tracker in \"failed\" state for resource \"01TESTLIFECYCLE0000000243\"\n When I attempt to transition from \"failed\" to \"running\"\n Then the transition should raise ValueError\n\n Scenario: Invalid transition discovered to stopping is rejected \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000244\"\n When I attempt to transition from \"discovered\" to \"stopping\"\n Then the transition should raise ValueError\n\n Scenario: Invalid transition building to discovered is rejected \n Given a lifecycle tracker in \"building\" state for resource \"01TESTLIFECYCLE0000000245\"\n When I attempt to transition from \"building\" to \"discovered\"\n Then the transition should raise ValueError\n\n Scenario: Parse devcontainer up output with log lines before JSON \n When I parse devcontainer up output with log lines before JSON\n Then the parsed container ID should be \"aabbccddee0099887766\"\n And the parsed workspace path should be \"/workspaces/multi\"\n\n Scenario: Transition history is capped at 100 entries \n Given a lifecycle tracker for resource \"01TESTLIFECYCLE0000000300\"\n When I perform 105 transitions cycling through valid states\n Then the tracker should have at most 100 transitions\n\n Scenario: Invalid container ID format is rejected during parsing \n When I parse devcontainer up output with invalid container ID \"not-a-hex-id!\"\n Then the parsed container ID should be empty\n And built-in types are bootstrapped\n\n Scenario: Parse devcontainer up output with relative workspace path \n When I parse devcontainer up output with relative workspace \"relative/ws\"\n Then the parsed container ID should be \"aabbccddee0011223344\"\n Given a devcontainer-instance resource is registered\n And the parsed workspace path should be None\n When I run resource list with all flag\n\n Scenario: Terminal tracker eviction removes oldest stopped trackers \n Given 210 stopped container trackers in the registry\n When I run terminal tracker eviction\n Then the registry should have at most 200 terminal trackers\n\n Scenario: evict_terminal_trackers returns 0 when below threshold \n Given 50 stopped container trackers in the registry\n When I run terminal tracker eviction\n Then the eviction count should be 0\n\n Scenario: get_lifecycle_tracker auto-creates tracker for new resource \n When I get tracker for new resource \"01TESTLIFECYCLE0000000730\"\n Then the auto-created tracker state should be \"discovered\"\n And the auto-created tracker resource_id should be \"01TESTLIFECYCLE0000000730\"\n Then the resource output should contain \"Status\"\n\n Scenario: clear_lifecycle_registry stops health check threads \n Given a mock devcontainer CLI runner\n And the runner configured for successful health check\n And an active container \"01TESTLIFECYCLE0000000740\" with container ID \"ctr-clr\"\n\n Scenario: resource list shows \"discovered (not built)\" for a new devcontainer-instance \n Given a fresh in-memory resource registry\n And a running health check for \"01TESTLIFECYCLE0000000740\"\n When I clear the lifecycle registry\n Then the registry should be empty\n And the health check for \"01TESTLIFECYCLE0000000740\" should be unregistered\n\n1 feature passed, 0 failed, 0 skipped\n32 scenarios passed, 0 failed, 0 skipped\n106 steps passed, 0 failed, 0 skipped\nTook 0min 0.037s\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n When I run resource list with all flag\n Then the resource output should contain \"discovered (not built)\"\n\n Scenario: resource list shows \"running\" state for a running devcontainer \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n And the devcontainer lifecycle state is \"running\"\n When I run resource list with all flag\n Then the resource output should contain \"running\"\n\n Scenario: resource list shows \"stopped\" state for a stopped devcontainer \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n And the devcontainer lifecycle state is \"stopped\"\n When I run resource list with all flag\n Then the resource output should contain \"stopped\"\n\n Scenario: resource list shows \"failed\" state for a failed devcontainer \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n And the devcontainer lifecycle state is \"failed\"\n When I run resource list with all flag\n Then the resource output should contain \"failed\"\n\n Scenario: resource list shows empty state for non-container resources \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given I run resource add \"git-checkout\" \"local/my-repo\" with path \"/tmp/repo\"\n When I run resource list\n Then the resource output should contain \"local/my-repo\"\n And the resource output should contain \"git-checkout\"\n\n Scenario: resource list shows warning banner when devcontainer is in detected state \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n When I run resource list with all flag\n Then the resource output should contain \"Devcontainer detected\"\n ASSERT FAILED: Expected 'Devcontainer detected' in output, got:\n Resources (1 total) \n ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓\n ┃ ID ┃ Name ┃ Type ┃ Status ┃ Kind ┃ Location ┃ Description ┃\n ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩\n │ 01KP2TDV4GAR... │ (unnamed) │ devcontainer-instance │ discovered (not built) │ physical │ /tmp/test-project │ Test devcontainer │\n └─────────────────┴───────────┴───────────────────────┴────────────────────────┴──────────┴───────────────────┴───────────────────┘\n\n\n Scenario: resource list does not show warning banner when devcontainer is running \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n And the devcontainer lifecycle state is \"running\"\n When I run resource list with all flag\n Then the resource output should not contain \"Devcontainer detected\"\n\n @tdd_issue @tdd_issue_4263 @tdd_expected_fail\n Scenario: resource list JSON output includes lifecycle_state for devcontainer-instance \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a devcontainer-instance resource is registered\n When I run resource list with all flag and format \"json\"\n Then the resource output should be valid JSON\n And the resource JSON output should contain key \"lifecycle_state\"\n ASSERT FAILED: Key 'lifecycle_state' not found in JSON output\n\n\n Scenario: resource list JSON output has null lifecycle_state for non-container resources \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given I run resource add \"git-checkout\" \"local/my-repo\" with path \"/tmp/repo\"\n When I run resource list with format \"json\"\n Then the resource output should be valid JSON\n And the resource JSON lifecycle_state should be null for non-container resources\n\n Scenario: resource list shows lifecycle state for container-instance resources \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\n Given a container-instance resource is registered\n When I run resource list with all flag\n Then the resource output should contain \"Status\"\n And the resource output should contain \"discovered (not built)\"\n\n Scenario: resource list succeeds gracefully when lifecycle tracker raises an error \n Given a fresh in-memory resource registry\n And built-in types are bootstrapped\nFeature: Devcontainer Resource Handler\n As a CleverAgents developer\n I want devcontainer resources to be auto-discovered and manually registered\n So that devcontainer environments integrate into the resource model Given a devcontainer-instance resource is registered\n\n Scenario: Register devcontainer-instance manually \n Given a temporary directory with a valid devcontainer.json\n When I create a devcontainer-instance resource at that path\n Then And the resource should have type \"the lifecycle tracker raises an error for the resourcedevcontainer-instance\"\n\n And the resource should have a non-empty resource_id\n When I run resource list with all flag\n Then the resource list command should succeed\n\n Scenario: Register container-instance manually \n When I create a container-instance resource with image \"ubuntu:latest\"\n Then the container resource should have type \"container-instance\"\n And the resource output should contain \"devcontainer-instance\"\n\n\nFailing scenarios:\n features/resource_list_lifecycle_state.feature:44 resource list shows warning banner when devcontainer is in detected state\n\n0 features passed, 1 failed, 0 skipped\n11 scenarios passed, 1 failed, 0 skipped\n69 steps passed, 1 failed, 0 skipped\nTook 0min 1.827s\n\n Scenario: Auto-discover devcontainer from git-checkout resource \n Given a temporary directory simulating a git checkout\n And the directory has a .devcontainer/devcontainer.json file\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 1 result\n And the discovered config path should end with \"devcontainer.json\"\n\n Scenario: Auto-discover root devcontainer.json from git-checkout \n Given a temporary directory simulating a git checkout\n And the directory has a root .devcontainer.json file\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 1 result\n\n Scenario: Auto-discover both devcontainer locations \n Given a temporary directory simulating a git checkout\n And the directory has a .devcontainer/devcontainer.json file\n And the directory has a root .devcontainer.json file\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 2 results\n\n Scenario: Auto-discover devcontainer from fs-directory resource \n Given a temporary directory simulating a filesystem mount\n And the directory has a .devcontainer/devcontainer.json file\n When I run devcontainer discovery on the directory as \"fs-directory\"\n Then discovery should return 1 result\n\n Scenario: No discovery for non-trigger resource types \n Given a temporary directory simulating a filesystem mount\n And the directory has a .devcontainer/devcontainer.json file\n When I run devcontainer discovery on the directory as \"fs-file\"\n Then discovery should return 0 results\n\n Scenario: Skip invalid JSON in devcontainer.json \n Given a temporary directory with an invalid devcontainer.json\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 0 results\n\n Scenario: Skip non-object JSON in devcontainer.json \n Given a temporary directory with a non-object devcontainer.json\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 0 results\n\n Scenario: Handle missing devcontainer directory gracefully \n Given a temporary directory with no devcontainer configuration\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 0 results\n@clone-into\nFeature: container-instance --clone-into argument\n As a CleverAgents user\n I want to clone a git repository into a running container\n So that I can work with the repository inside the container environment\n Scenario: Devcontainer types appear in resource type list \n Given the built-in resource type registry\n\n Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.1 \n When I validate clone-into URL \"https://github.com/org/repo.git\"\n When I list all built-in type names\n Then the clone-into URL should be valid\n Then the type list should contain \"devcontainer-instance\"\n And the type list should contain \"devcontainer-file\"\n And the type list should contain \"container-instance\"\n\n Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.2 \n When I validate clone-into URL \"http://internal.example.com/repo.git\"\n Then the clone-into URL should be valid\n\n Scenario: DevcontainerHandler satisfies ResourceHandler protocol \n When I instantiate DevcontainerHandler\n Then it should satisfy the ResourceHandler protocol\n\n Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.3 \n When I validate clone-into URL \"git@github.com:org/repo.git\"\n Then the clone-into URL should be valid\n\n Scenario: DevcontainerHandler has correct default strategy \n When I instantiate DevcontainerHandler\n\n Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.4 \n When I validate clone-into URL \"ssh://git@bitbucket.org/org/repo.git\"\n Then the clone-into URL should be valid\n Then its default strategy should be \"snapshot\"\n\n Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.5 \n When I validate clone-into URL \"git://github.com/org/repo.git\"\n\n Scenario: DevcontainerHandler has correct type label \n When I instantiate DevcontainerHandler\n Then the clone-into URL should be valid\n Then its type label should be \"devcontainer\"\n\n Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.6 \n When I validate clone-into URL \"/local/path/to/repo\"\n Then the clone-into URL should be valid\n\n Scenario: Discovery result requires valid config_path \n When I create a discovery result with valid parameters\n Then the result should have a config_path attribute\n\n Scenario Outline: validate_clone_into_url accepts valid git URLs -- @1.7 \n When I validate clone-into URL \"./relative/repo\"\n Then the clone-into URL should be valid\n\n Scenario: Discovery result rejects empty parent_location \n When I create a discovery result with empty parent_location\n\n Scenario Outline: validate_clone_into_url rejects invalid URLs -- @1.1 \n When I validate clone-into URL \"not-a-url\"\n Then it should raise a ValueError\n Then the clone-into URL should be invalid\n\n Scenario Outline: validate_clone_into_url rejects invalid URLs -- @1.2 \n When I validate clone-into URL \"ftp://example.com\"\n\n Scenario: Auto-discover named devcontainer configuration \n Given a temporary directory simulating a git checkout\n Then the clone-into URL should be invalid\n And the directory has a named devcontainer configuration \"api\"\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 1 result\n And the first result should have config name \"api\"\n\n Scenario: clone_repo_into_container rejects empty container_id \n When I call clone_repo_into_container with empty container_id\n Then it should raise ValueError mentioning \"container_id\"\n\n Scenario: Auto-discover multiple named devcontainer configurations \n Given a temporary directory simulating a git checkout\n And the directory has a named devcontainer configuration \"api\"\n\n Scenario: clone_repo_into_container rejects empty repo_url \n When I call clone_repo_into_container with empty repo_url\n And the directory has a named devcontainer configuration \"frontend\"\n Then it should raise ValueError mentioning \"repo_url\"\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 2 results\n And the result config names should include \"api\"\n\n Scenario: clone_repo_into_container succeeds when docker exec returns 0 \n Given a mock docker exec that succeeds for clone\n And the result config names should include \"frontend\"\n When I clone \"https://github.com/org/repo.git\" into container \"abc123def456\"\n Then the clone should succeed\n And the clone target should be \"/workspace\"\n\n Scenario: Auto-discover mixed root and named devcontainer configurations \n Given a temporary directory simulating a git checkout\n And the directory has a .devcontainer/devcontainer.json file\n\n Scenario: clone_repo_into_container uses custom target directory \n Given a mock docker exec that succeeds for clone\n And the directory has a named devcontainer configuration \"api\"\n When I clone repo \"https://github.com/org/repo.git\" into container \"abc123def456\" with target \"/custom/dir\"\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then the clone should succeed\n And the clone target should be \"/custom/dir\"\n Then discovery should return 2 results\n\n Scenario: Root devcontainer.json has no config name \n Given a temporary directory simulating a git checkout\n And the directory has a .devcontainer/devcontainer.json file\n\n Scenario: clone_repo_into_container raises CloneIntoError when docker exec fails \n Given a mock docker exec that fails for clone with stderr \"fatal: repository not found\"\n When I clone \"https://github.com/org/missing.git\" into container \"abc123def456\"\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then the clone should raise CloneIntoError\n And the CloneIntoError should mention \"repository not found\"\n Then discovery should return 1 result\n And the first result should have no config name\n\n Scenario: CloneIntoError stores repo_url, container_id, and stderr \n Given a CloneIntoError with repo \"https://example.com/repo.git\" container \"ctr-001\" stderr \"auth failed\"\n Then the CloneIntoError repo_url should be \"https://example.com/repo.git\"\n And the CloneIntoError container_id should be \"ctr-001\"\n And the CloneIntoError stderr should be \"auth failed\"\n\n Scenario: Root .devcontainer.json has no config name \n Given a temporary directory simulating a git checkout\n And the directory has a root .devcontainer.json file\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 1 result\n And \nthe first result should have no config name Scenario: container-instance resource type has clone-into CLI argument \n\n When I look up the \"container-instance\" resource type spec\n Then the spec should have a CLI argument named \"clone-into\"\n And the clone-into argument should be of type \"string\"\n And the clone-into argument should not be required\n\n Scenario: Named config with invalid JSON is skipped \n Given a temporary directory simulating a git checkout\n And the directory has a named devcontainer configuration \"broken\" with invalid JSON\n\n Scenario: validate_clone_into_url rejects whitespace-only string \n When I validate clone-into URL with whitespace only\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then the clone-into URL should be invalid\n\n1 feature passed, 0 failed, 0 skipped\n17 scenarios passed, 0 failed, 0 skipped\n44 steps passed, 0 failed, 0 skipped\nTook 0min 0.008s\n Then discovery should return 0 results\n\n Scenario: Empty .devcontainer directory returns no results \n Given a temporary directory simulating a git checkout\n And the directory has an empty .devcontainer directory\n When I run devcontainer discovery on the directory as \"git-checkout\"\n Then discovery should return 0 results\n\n Scenario: git-checkout is a trigger type \n Then \"git-checkout\" should be a trigger type\n\n Scenario: fs-directory is a trigger type \n Then \"fs-directory\" should be a trigger type\n\n Scenario: devcontainer-instance is not a trigger type \n Then \"devcontainer-instance\" should not be a trigger type\n\n1 feature passed, 0 failed, 0 skipped\n26 scenarios passed, 0 failed, 0 skipped\n90 steps passed, 0 failed, 0 skipped\nTook 0min 0.026s\nFeature: Devcontainer Session Cleanup and CLI Commands\n As a CleverAgents developer\n I want session-scoped container cleanup and CLI stop/rebuild commands\n So that containers are cleaned up when sessions end and can be managed via CLI\n Scenario: Active containers stopped on session cleanup \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000040\" with container ID \"ctr-a\"\n And an active container \"01TESTLIFECYCLE0000000041\" with container ID \"ctr-b\"\n When I run session cleanup for session \"session-001\"\n Then the container state for \"01TESTLIFECYCLE0000000040\" should be \"stopped\"\n And the container state for \"01TESTLIFECYCLE0000000041\" should be \"stopped\"\n\n Scenario: CleanupService stop_active_devcontainers is callable and returns list \n When I call CleanupService stop_active_devcontainers with no active containers\n Then the cleanup result should be an empty list\n And CleanupService should have a stop_active_devcontainers method\n\n Scenario: CleanupService stop_active_devcontainers calls handler cleanup \n When I call CleanupService stop_active_devcontainers with no active containers\n Then the cleanup result should be an empty list\n\n Scenario: Session cleanup handles stop failure gracefully \n Given a mock devcontainer CLI runner\n And the runner configured to raise exception on stop\n And an active container \"01TESTLIFECYCLE0000000130\" with container ID \"ctr-fail\"\n When I run session cleanup for session \"session-err\"\n Then the cleanup should return 0 stopped containers\n\n Scenario: CLI stop succeeds for active devcontainer-instance \n Given a mock resource service with devcontainer \"local/test-dc\" id \"01TESTLIFECYCLE0000000200\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000200\" with container ID \"ctr-cli-stop\"\n When I invoke CLI resource stop \"local/test-dc\"\nFeature: DevcontainerHandler missing protocol methods\n As a developer using DevcontainerHandler\n I want delete(), list_children(), diff(), and create_sandbox() to be implemented\n So that the handler satisfies the full ResourceHandler protocol (issue #1242)\n Scenario: dcproto delete file successfully from devcontainer \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful rm output\n When dcproto I delete path \"src/old_file.py\" from the resource\n Then dcproto the delete should succeed\n And dcproto the delete message should contain \"src/old_file.py\"\n And dcproto the subprocess should have been called with \"rm\" and \"src/old_file.py\"\n\n Scenario: dcproto delete with empty path targets workspace root \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful rm output\n When dcproto I delete with empty path from the resource\n Then dcproto the delete should succeed\n And dcproto the subprocess should have been called with \"rm\" and \".\"\n\n Scenario: dcproto delete fails when container is stopped \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns failed rm output with stderr \"container not running\"\n When dcproto I delete path \"file.txt\" from the resource\n Then the CLI exit code should be 0\n And the CLI output should contain \"Stopped\"\n Then dcproto the delete should fail\n And dcproto the delete failure message should contain \"container not running\"\n\n Scenario: CLI stop rejects non-devcontainer resource type \n Given a mock resource service with git-checkout \"local/my-repo\" id \"01TESTLIFECYCLE0000000201\"\n When I invoke CLI resource stop \"local/my-repo\"\n\n Scenario: dcproto delete from resource with no location raises ValueError \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with no location\n When dcproto I delete path \"any/file.txt\" from the resource\n Then dcproto the delete should raise ValueError containing \"no location\"\n\n Scenario: dcproto list_children returns sorted names from devcontainer \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful ls listing \"src\\ntests\\nREADME.md\"\n When dcproto I list children of the resource\n Then dcproto the children list should have 3 entries\n And dcproto the children list should be \"README.md,src,tests\"\n\n Scenario: dcproto list_children returns empty list when container is stopped \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns failed ls output\n When dcproto I list children of the resource\n Then dcproto the children list should have 0 entries\n\n Scenario: dcproto list_children filters blank lines from ls output \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful ls listing \"alpha\\n\\n\\nbeta\\n\"\n When dcproto I list children of the resource\n Then dcproto the children list should have 2 entries\n And dcproto the children list should be \"alpha,beta\"\n\n Scenario: dcproto list_children from resource with no location raises ValueError \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with no location\n When dcproto I list children of the resource\n Then dcproto the list children should raise ValueError containing \"no location\"\n\n Scenario: dcproto list_children uses devcontainer exec ls command \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto subprocess returns successful ls listing \"main.py\"\n When dcproto I list children of the resource\n Then dcproto the subprocess should have been called with \"ls\" and \"--workspace-folder\"\n\n Scenario: dcproto diff detects changes when hashes differ \n Given dcproto a devcontainer handler\n Then the devcontainer CLI exit code should be non-zero\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And the CLI output should contain \"not a stoppable container type\"\n And dcproto the container content hash is \"aabbccdd\"\n And dcproto a temporary directory with a different file\n When dcproto I diff the resource against the temporary directory\n Then dcproto the diff should report has_changes true\n And dcproto the diff files_changed should be 1\n\n Scenario: CLI rebuild succeeds for stopped devcontainer-instance \n Given a mock resource service with devcontainer \"local/test-dc-rb\" id \"01TESTLIFECYCLE0000000210\" at \"/workspace/project\"\n And a stopped container \"01TESTLIFECYCLE0000000210\"\n When I invoke CLI resource rebuild \"local/test-dc-rb\"\n\n Scenario: dcproto diff reports no changes when hashes match \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto the container content hash matches the other location\n When dcproto I diff the resource against the same location\n Then dcproto the diff should report has_changes false\n And dcproto the diff files_changed should be 0\n Then the CLI exit code should be 0\n And the CLI output should contain \"Rebuilt\"\n\n Scenario: dcproto diff reports no changes when both sides are absent \n Given dcproto a devcontainer handler\n\n Scenario: CLI rebuild rejects resource with no location \n Given a mock resource service with devcontainer \"local/dc-no-loc\" id \"01TESTLIFECYCLE0000000211\" with no location\n And dcproto a devcontainer-instance resource with location \"/nonexistent/path\"\n And a stopped container \"01TESTLIFECYCLE0000000211\"\n When dcproto I diff the resource against \"/another/nonexistent/path\"\n When I invoke CLI resource rebuild \"local/dc-no-loc\"\n Then dcproto the diff should report has_changes false\n\n Scenario: dcproto diff from resource with no location raises ValueError \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with no location\n When dcproto I diff the resource against \"/some/path\"\n Then the devcontainer CLI exit code should be non-zero\n Then dcproto the diff should raise ValueError containing \"no location\"\n And the CLI output should contain \"no location\"\n\n Scenario: dcproto diff unified_diff is always empty string \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n\n Scenario: CLI rebuild rejects non-devcontainer resource type \n Given a mock resource service with git-checkout \"local/git-rb\" id \"01TESTLIFECYCLE0000000212\"\n And dcproto the container content hash is \"aabbccdd\"\n And dcproto a temporary directory with a different file\n When I invoke CLI resource rebuild \"local/git-rb\"\n When dcproto I diff the resource against the temporary directory\n Then dcproto the diff unified_diff should be empty\n\n Scenario: dcproto create_sandbox delegates to base class for running container \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto the container is in RUNNING state\n And dcproto a mock sandbox manager that returns a valid sandbox\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"rebuild requires devcontainer\"\n When dcproto I create a sandbox for the resource with plan \"plan-001\"\n Then dcproto the sandbox result should have strategy \"snapshot\"\n And dcproto the sandbox result should have a sandbox path\n\n Scenario: CLI stop calls stop_container with correct resource_id \n Given a mock resource service with devcontainer \"local/test-dc-r7\" id \"01TESTLIFECYCLE0000000220\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000220\" with container ID \"ctr-r7\"\n When I invoke CLI resource stop \"local/test-dc-r7\"\n\n Scenario: dcproto create_sandbox activates container when in DETECTED state \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto the container is in DETECTED state\n And dcproto a mock sandbox manager that returns a valid sandbox\n And dcproto activate_container is mocked to succeed\n When dcproto I create a sandbox for the resource with plan \"plan-002\"\n Then dcproto activate_container should have been called\n Then the CLI exit code should be 0\n And the CLI stop mock should have been called with \"01TESTLIFECYCLE0000000220\"\n\n Scenario: dcproto create_sandbox activates container when in STOPPED state \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with location \"/ws/project\"\n And dcproto the container is in STOPPED state\n And dcproto a mock sandbox manager that returns a valid sandbox\n And dcproto activate_container is mocked to succeed\n When dcproto I create a sandbox for the resource with plan \"plan-003\"\n\n Scenario: CLI rebuild calls rebuild_container with correct args \n Given a mock resource service with devcontainer \"local/test-dc-r7rb\" id \"01TESTLIFECYCLE0000000221\" at \"/workspace/project\"\n And a stopped container \"01TESTLIFECYCLE0000000221\"\n Then dcproto activate_container should have been called\n When I invoke CLI resource rebuild \"local/test-dc-r7rb\"\n\n Scenario: dcproto create_sandbox raises ValueError for resource with no location \n Given dcproto a devcontainer handler\n And dcproto a devcontainer-instance resource with no location\n And dcproto a mock sandbox manager that returns a valid sandbox\n When dcproto I create a sandbox for the resource with plan \"plan-004\"\n Then dcproto the create_sandbox should raise ValueError containing \"no location\"\n\n1 feature passed, 0 failed, 0 skipped\n18 scenarios passed, 0 failed, 0 skipped\n102 steps passed, 0 failed, 0 skipped\nTook 0min 0.028s\n Then the CLI exit code should be 0\n And the CLI rebuild mock should have been called with \"01TESTLIFECYCLE0000000221\" and \"/workspace/project\"\n\n Scenario: DevcontainerHandler has expected class attributes and is instantiable \n Then DevcontainerHandler should have _default_strategy \"snapshot\"\n And DevcontainerHandler should have _type_label \"devcontainer\"\n And DevcontainerHandler should be instantiable\n\n Scenario: DevcontainerHandler extends BaseResourceHandler and inherits resolve interface \n Then DevcontainerHandler should be a subclass of BaseResourceHandler\n And DevcontainerHandler instance should have a resolve method\n\n Scenario: CLI stop rejects container not in running state \n Given a mock resource service with devcontainer \"local/dc-det\" id \"01TESTLIFECYCLE0000000230\" at \"/workspace/project\"\n And a lifecycle tracker for resource \"01TESTLIFECYCLE0000000230\"\n When I invoke CLI resource stop \"local/dc-det\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Cannot stop\"\n\n Scenario: CLI rebuild rejects container in running state \n Given a mock resource service with devcontainer \"local/dc-run\" id \"01TESTLIFECYCLE0000000231\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000231\" with container ID \"ctr-rb-state\"\n When I invoke CLI resource rebuild \"local/dc-run\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Cannot rebuild\"\n\n Scenario: Session cleanup returns list of stopped resource IDs \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000250\" with container ID \"ctr-r14a\"\n And an active container \"01TESTLIFECYCLE0000000251\" with container ID \"ctr-r14b\"\n When I run session cleanup for session \"session-r14\"\n Then the cleanup stopped list should contain \"01TESTLIFECYCLE0000000250\"\n And the cleanup stopped list should contain \"01TESTLIFECYCLE0000000251\"\n\n Scenario: CLI stop with --yes skips confirmation and calls stop_container \n Given a CLI-mockable running devcontainer resource \"test-dc-yes-stop\"\n When I invoke CLI resource stop \"test-dc-yes-stop\"\n Then the devcontainer CLI exit code should be 0\n And the CLI stop mock should have been called once\n\n Scenario: CLI rebuild with --yes skips confirmation and calls rebuild_container \n Given a CLI-mockable stopped devcontainer resource \"test-dc-yes-rebuild\"\n When I invoke CLI resource rebuild \"test-dc-yes-rebuild\"\n Then the devcontainer CLI exit code should be 0\n And the CLI rebuild mock should have been called once\n\n Scenario: CLI stop accepts container-instance type (issue #2588 fix) \n Given a mock resource service with container-instance \"local/ctr-stop\" id \"01TESTLIFECYCLE0000000370\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000370\" with container ID \"ctr-ci-stop\"\n When I invoke CLI resource stop \"local/ctr-stop\"\n Then the CLI exit code should be 0\n And the CLI output should contain \"Stopped\"\n\n Scenario: CLI stop handles NotFoundError from show_resource \n Given a mock resource service that raises NotFoundError for \"local/missing\"\n When I invoke CLI resource stop \"local/missing\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Resource not found\"\n\n Scenario: CLI rebuild handles NotFoundError from show_resource \n Given a mock resource service that raises NotFoundError for \"local/missing-rb\"\n When I invoke CLI resource rebuild \"local/missing-rb\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Resource not found\"\n\n Scenario: CLI stop handles RuntimeError from stop_container \n Given a mock resource service with devcontainer \"local/dc-rterr\" id \"01TESTLIFECYCLE0000000500\" at \"/workspace/project\"\n And an active container \"01TESTLIFECYCLE0000000500\" with container ID \"ctr-rterr\"\n And CLI stop_container mock that raises RuntimeError\n When I invoke CLI resource stop with error mock \"local/dc-rterr\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Stop failed\"\n\n Scenario: CLI rebuild handles RuntimeError from rebuild_container \n Given a mock resource service with devcontainer \"local/dc-rberr\" id \"01TESTLIFECYCLE0000000501\" at \"/workspace/project\"\n And a stopped container \"01TESTLIFECYCLE0000000501\"\n And CLI rebuild_container mock that raises RuntimeError\n When I invoke CLI resource rebuild with error mock \"local/dc-rberr\"\n Then the devcontainer CLI exit code should be non-zero\n And the CLI output should contain \"Rebuild failed\"\n\n Scenario: Session cleanup only stops containers belonging to that session \n Given a mock devcontainer CLI runner\n And a session-scoped active container \"01TESTLIFECYCLE0000000600\" with ID \"ctr-s1\" session \"sess-A\"\n And a session-scoped active container \"01TESTLIFECYCLE0000000601\" with ID \"ctr-s2\" session \"sess-B\"\n When I run session cleanup for session \"sess-A\"\n Then the container state for \"01TESTLIFECYCLE0000000600\" should be \"stopped\"\n And the container state for \"01TESTLIFECYCLE0000000601\" should be \"running\"\n\n Scenario: Session cleanup with no session_id stops all containers \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000610\" with container ID \"ctr-all1\"\n And an active container \"01TESTLIFECYCLE0000000611\" with container ID \"ctr-all2\"\n When I run session cleanup with no session filter\n Then the container state for \"01TESTLIFECYCLE0000000610\" should be \"stopped\"\n And the container state for \"01TESTLIFECYCLE0000000611\" should be \"stopped\"\n\n Scenario: Session cleanup triggers terminal tracker eviction \n Given a mock devcontainer CLI runner\n And 210 stopped container trackers in the registry\n And an active container \"01TESTLIFECYCLE0000000650\" with container ID \"ctr-evict\"\n When I run session cleanup for session \"session-evict\"\n Then the container state for \"01TESTLIFECYCLE0000000650\" should be \"stopped\"\n And the registry should have at most 200 terminal trackers\n\n Scenario: list_active_containers_for_session returns only matching session \n Given a mock devcontainer CLI runner\n And a session-scoped active container \"01TESTLIFECYCLE0000000700\" with ID \"ctr-ts3a\" session \"sess-X\"\n And a session-scoped active container \"01TESTLIFECYCLE0000000701\" with ID \"ctr-ts3b\" session \"sess-Y\"\n When I list active containers for session \"sess-X\"\n Then the session container list should contain \"01TESTLIFECYCLE0000000700\"\n And the session container list should not contain \"01TESTLIFECYCLE0000000701\"\n\n Scenario: list_active_containers_for_session with empty session returns all \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000710\" with container ID \"ctr-ts3c\"\n And an active container \"01TESTLIFECYCLE0000000711\" with container ID \"ctr-ts3d\"\n When I list active containers for an empty session\n Then the session container list should contain \"01TESTLIFECYCLE0000000710\"\n And the session container list should contain \"01TESTLIFECYCLE0000000711\"\n\n Scenario: list_active_containers_for_session includes unscoped containers \n Given a mock devcontainer CLI runner\n And an active container \"01TESTLIFECYCLE0000000720\" with container ID \"ctr-unscoped\"\n And a session-scoped active container \"01TESTLIFECYCLE0000000721\" with ID \"ctr-scoped\" session \"sess-Z\"\n When I list active containers for session \"sess-Z\"\n Then the session container list should contain \"01TESTLIFECYCLE0000000720\"\n And the session container list should contain \"01TESTLIFECYCLE0000000721\"\n\n Scenario: Session close cleans up containers even without session service \n Given a facade with no session service\n And a session-scoped active container \"01TESTLIFECYCLE0000000630\" with no docker ID session \"sess-f4\"\n When I close session \"sess-f4\" via the facade\n Then the container state for \"01TESTLIFECYCLE0000000630\" should be \"stopped\"\n\n1 feature passed, 0 failed, 0 skipped\n30 scenarios passed, 0 failed, 0 skipped\n143 steps passed, 0 failed, 0 skipped\nTook 0min 0.436s\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n2026-04-13 07:00:42 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:42 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:42 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:42 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n\u001b[1mIncluding unscoped container 01TESTLIFECYCLE0000000040 in session 'session-001' cleanup (container has no session_id set)\u001b[0m\n\u001b[1mIncluding unscoped container 01TESTLIFECYCLE0000000041 in session 'session-001' cleanup (container has no session_id set)\u001b[0m\n\u001b[1mContainer 01TESTLIFECYCLE0000000040: running -> stopping (manual stop)\u001b[0m\n\u001b[1mContainer 01TESTLIFECYCLE0000000040: stopping -> stopped (container stopped)\u001b[0m\n\u001b[1mStopped container 01TESTLIFECYCLE0000000040 (session=session-001)\u001b[0m\n\u001b[1mContainer 01TESTLIFECYCLE0000000041: running -> stopping (manual stop)\u001b[0m\n\u001b[1mContainer 01TESTLIFECYCLE0000000041: stopping -> stopped (container stopped)\u001b[0m\n\u001b[1mStopped container 01TESTLIFECYCLE0000000041 (session=session-001)\u001b[0m\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n\u001b[1mRegistered built-in resource type: fs-mount\u001b[0m\n\u001b[1mRegistered built-in resource type: git-checkout\u001b[0m\n\u001b[1mRegistered built-in resource type: fs-directory\u001b[0m\n\u001b[1mRegistered built-in resource type: container-instance\u001b[0m\n\u001b[1mRegistered built-in resource type: devcontainer-instance\u001b[0m\n\u001b[1mRegistered built-in resource type: devcontainer-file\u001b[0m\n\u001b[1mRegistered built-in resource type: git\u001b[0m\n\u001b[1mRegistered built-in resource type: git-remote\u001b[0m\n\u001b[1mRegistered built-in resource type: git-branch\u001b[0m\n\u001b[1mRegistered built-in resource type: git-tag\u001b[0m\n\u001b[1mRegistered built-in resource type: git-commit\u001b[0m\n\u001b[1mRegistered built-in resource type: git-tree\u001b[0m\n\u001b[1mRegistered built-in resource type: git-tree-entry\u001b[0m\n\u001b[1mRegistered built-in resource type: git-stash\u001b[0m\n\u001b[1mRegistered built-in resource type: git-submodule\u001b[0m\n\u001b[1mRegistered built-in resource type: fs-symlink\u001b[0m\n\u001b[1mRegistered built-in resource type: fs-hardlink\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-account\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-region\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-network\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-subnet\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-security-group\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-load-balancer\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-compute-instance\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-object-store\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-block-storage\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-identity-principal\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-role\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-policy\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-log-group\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-alarm\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-queue\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-topic\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-container-repo\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-container-cluster\u001b[0m\n\u001b[1mRegistered built-in resource type: cloud-container-service\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-account\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-region\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-vpc\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-subnet\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-igw\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-nat-gw\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-route-table\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-nacl\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-security-group\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-alb\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-nlb\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-target-group\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-listener\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ec2-instance\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ami\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-launch-template\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-asg\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-s3-bucket\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ebs-volume\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-efs-filesystem\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-iam-user\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-iam-role\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-iam-policy\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-iam-instance-profile\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-cloudwatch-log-group\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-cloudwatch-alarm\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-cloudwatch-metric\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eventbridge-bus\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eventbridge-rule\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eventbridge-target\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-sqs-queue\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-sns-topic\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-sns-subscription\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ecr-repo\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ecs-cluster\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ecs-service\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-ecs-task-def\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eks-cluster\u001b[0m\n\u001b[1mRegistered built-in resource type: aws-eks-nodegroup\u001b[0m\n\u001b[1mRegistered built-in resource type: gcp\u001b[0m\n\u001b[1mRegistered built-in resource type: azure\u001b[0m\n\u001b[1mRegistered built-in resource type: container-runtime\u001b[0m\n\u001b[1mRegistered built-in resource type: container-image\u001b[0m\n\u001b[1mRegistered built-in resource type: container-mount\u001b[0m\n\u001b[1mRegistered built-in resource type: container-exec-env\u001b[0m\n\u001b[1mRegistered built-in resource type: container-port\u001b[0m\n\u001b[1mRegistered built-in resource type: container-volume\u001b[0m\n\u001b[1mRegistered built-in resource type: container-network\u001b[0m\n\u001b[1mRegistered built-in resource type: postgres\u001b[0m\n\u001b[1mRegistered built-in resource type: mysql\u001b[0m\n\u001b[1mRegistered built-in resource type: sqlite\u001b[0m\n\u001b[1mRegistered built-in resource type: duckdb\u001b[0m\n\u001b[1mRegistered built-in resource type: file\u001b[0m\n\u001b[1mRegistered built-in resource type: directory\u001b[0m\n\u001b[1mRegistered built-in resource type: commit\u001b[0m\n\u001b[1mRegistered built-in resource type: branch\u001b[0m\n\u001b[1mRegistered built-in resource type: tag\u001b[0m\n\u001b[1mRegistered built-in resource type: tree\u001b[0m\n\u001b[1mRegistered built-in resource type: remote\u001b[0m\n\u001b[1mRegistered built-in resource type: submodule\u001b[0m\n\u001b[1mRegistered built-in resource type: symlink\u001b[0m\n\u001b[1mRegistered built-in resource type: executable\u001b[0m\n\u001b[1mRegistered built-in resource type: lsp-server\u001b[0m\n\u001b[1mRegistered built-in resource type: lsp-workspace\u001b[0m\n\u001b[1mRegistered built-in resource type: lsp-document\u001b[0m\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.created child_domain=uko-oo: parent_domain=uko-code:\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=MEMBER_LISTING domain=uko-oo: new_level=CLASS_HIERARCHY\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.insert_after after_level=SIGNATURES_WITH_DOCS domain=uko-oo: new_level=VISIBILITY_ANNOTATED\n2026-04-13 07:00:43 [debug ] detail_level_map_builder.built domain=uko-oo: max_depth=11 num_levels=12\n\nOverall summary:\n6 features passed, 1 failed, 0 errored, 0 skipped\n144 scenarios passed, 1 failed, 0 errored, 0 skipped\n553 steps passed, 1 failed, 0 errored, 0 skipped\nTook 2.366s\nWall time: 2m 47.786s\n\nnox > Running session unit_tests-3.13\nnox > Reusing existing virtual environment at .nox/unit_tests-3-13.\nnox > uv pip install -e '.[tests]'\nnox > uv pip install setuptools wheel\nnox > uv pip install .nox/unit_tests-3-13/tmp/behave-parallel-inprocess\nnox > python scripts/create_template_db.py /tmp/impl-worker-7555-20260413/build/.template-migrated.db\nnox > python -m compileall -q features/\nnox > /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature\nnox > Command /tmp/impl-worker-7555-20260413/.nox/unit_tests-3-13/bin/behave-parallel -q --processes 32 features/container_clone_into.feature features/devcontainer_sandbox_strategy.feature features/devcontainer_state.feature features/devcontainer_handler.feature features/devcontainer_cleanup.feature features/resource_list_lifecycle_state.feature features/devcontainer_handler_protocol_methods.feature failed with exit code 1\nnox > Session unit_tests-3.13 failed." +} \ No newline at end of file