Files
cleveragents-core/features/devcontainer_state.feature
T
CoreRasurae cf67ba0a86
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 18s
CI / security (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 37s
CI / unit_tests (pull_request) Successful in 2m27s
CI / docker (pull_request) Successful in 49s
CI / integration_tests (pull_request) Successful in 3m46s
CI / coverage (pull_request) Successful in 4m56s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 14s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 32s
CI / typecheck (push) Successful in 36s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 4m7s
CI / integration_tests (push) Successful in 4m41s
CI / docker (push) Successful in 44s
CI / coverage (push) Successful in 4m56s
CI / benchmark-publish (push) Successful in 17m17s
CI / benchmark-regression (pull_request) Successful in 31m25s
feat(devcontainer): add lazy activation and lifecycle management
Implemented lazy container activation for devcontainer-instance resources
with ContainerLifecycleState enum tracking six states (inactive, starting,
active, stopping, stopped, error) with validated transitions. Extended
DevcontainerHandler with devcontainer up CLI integration and JSON output
parsing for container start. Added periodic health checking via
devcontainer exec ping with configurable interval. Added agents resource
stop and agents resource rebuild CLI commands for manual lifecycle
control. Wired session close and plan completion hooks to automatic
container cleanup. Includes lifecycle state persistence in resource
registry with timestamped transitions. Added Behave BDD tests, Robot
integration tests, and ASV activation latency benchmarks.

- Added remoteWorkspaceFolder absolute-path validation
- Aligned spec: handler name, rebuild types, --yes flag on stop/rebuild
- Added registry re-read in stop_container success path for consistency
- Added session_id field to ContainerLifecycleTracker for scoped cleanup
- Scoped stop_all_active_containers to session_id when provided
- Wired _cleanup_devcontainers into fail_apply and fail_execute
- Wired start_health_check into activate_container success path
- Restructured facade session close to always run container cleanup
  even without session service (F4)
- Re-read tracker from registry in activate_container success path
- Added evict_terminal_trackers to cap registry growth
- Updated devcontainer_resources.md: health check auto-start, scoped
  cleanup hooks, known limitations for eviction and sandbox_strategy
- Wired evict_terminal_trackers into stop_all_active_containers so
  terminal-state trackers are actually evicted in production
- Made stop_container idempotent: returns early when container is
  already in a terminal state instead of raising ValueError
- Fixed benchmark health check thread leak in TimeActivationLatency
  by clearing registry after each timing loop
- Added rebuild pass-through (--reset-container flag to devcontainer up)
- Added host_workspace_path field on ContainerLifecycleTracker so
  health probes use the host-side path for devcontainer exec
- Wired lazy activation into DevcontainerHandler.resolve() for
  devcontainer-instance resources in non-running states
- Changed _default_strategy from SNAPSHOT to NONE (container
  itself provides isolation; SandboxFactory raises NotImplementedError
  for snapshot)
- Restricted _STOPPABLE_TYPES to devcontainer-instance only
  (container-instance is not directly stoppable via CLI)

ISSUES CLOSED: #514
2026-03-10 12:17:51 +00:00

199 lines
9.6 KiB
Gherkin

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
# Lifecycle state model
Scenario: ContainerLifecycleState has all required values
When I inspect ContainerLifecycleState enum values
Then the lifecycle enum should contain "detected"
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"
Scenario: Valid transitions are accepted
Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000001"
When I transition from "detected" to "building"
Then the transition should succeed
And the tracker state should be "building"
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"
Scenario: Invalid transition is rejected
Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000003"
When I attempt to transition from "detected" to "running"
Then the transition should raise ValueError
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"
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"
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
Scenario: Transition history is recorded
Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000007"
When I transition from "detected" 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"
And I retrieve the tracker for "01TESTLIFECYCLE0000000060"
Then the retrieved tracker state should be "building"
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"
# ── JSON output parsing ────────────────────────────────────
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"
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
# ── Argument validation ────────────────────────────────────
Scenario: validate_transition rejects wrong types
When I call validate_transition with non-enum arguments
Then it should raise TypeError
Scenario: validate_transition rejects wrong to_state type
When I call validate_transition with non-enum to_state
Then it should raise TypeError
Scenario: transition_state rejects wrong tracker type
When I call transition_state with non-tracker argument
Then it should raise TypeError
Scenario: transition_state rejects wrong to_state type
When I call transition_state with non-enum to_state argument
Then it should raise TypeError
# ── Registry edge cases ────────────────────────────────────
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
Scenario: set_lifecycle_tracker rejects wrong type
When I attempt to set tracker with wrong type
Then the set tracker should raise TypeError
# ── R13: Additional invalid transition coverage ────────────
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
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
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
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
Scenario: Invalid transition detected to stopping is rejected
Given a lifecycle tracker for resource "01TESTLIFECYCLE0000000244"
When I attempt to transition from "detected" to "stopping"
Then the transition should raise ValueError
Scenario: Invalid transition building to detected is rejected
Given a lifecycle tracker in "building" state for resource "01TESTLIFECYCLE0000000245"
When I attempt to transition from "building" to "detected"
Then the transition should raise ValueError
# ── R4: Multi-line JSON output parsing ─────────────────────
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"
# ── T1: Transition history cap at 100 ──────────────────────
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
# ── S1: Container ID format validation ─────────────────────
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
# ── F11: Invalid workspace path rejected during parsing ────
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"
And the parsed workspace path should be None
# ── R7-F8: Terminal tracker eviction ────────────────────────
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
# ── TEST-3: Coverage gap scenarios ──────────────────────────
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
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"
And the auto-created tracker resource_id should be "01TESTLIFECYCLE0000000730"
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"
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