Files
cleveragents-core/features/git_worktree_sandbox.feature
khyari hamza cc45b5ff2c feat(sandbox): add git_worktree and copy_on_write sandbox strategies
Implement GitWorktreeSandbox (worktree creation, commit+merge, rollback,
cleanup, branch sanitization, git timeouts) and CopyOnWriteSandbox
(directory copy, diff-based commit, rollback, cleanup).

Update SandboxFactory to wire new strategies, remove stale overlay/
versioning constants, add snapshot placeholder, align resource type
mapping to spec types (git-checkout, fs-mount, fs-directory, fs-file).

Add 40 Behave scenarios (19 git_worktree + 21 copy_on_write) with gwt/cow
prefixed steps. Update existing sandbox_factory_coverage feature (21
scenarios) to reflect new factory behavior.

All quality gates pass: ruff, pyright, 2325 scenarios, 97% coverage.

TASK-006 / Stage B4
2026-02-14 04:01:20 +00:00

130 lines
5.5 KiB
Gherkin

Feature: Git worktree sandbox lifecycle
As a developer
I want a sandbox that isolates plan changes in a git worktree
So that the original repository is unmodified until changes are committed
Background:
Given a gwt test git repository is initialised
# --- Creation ---
Scenario: Create a git worktree sandbox
When a gwt sandbox is created for plan "plan-001"
Then the gwt sandbox should be in the "created" state
And the gwt sandbox context should reference plan "plan-001"
And the gwt sandbox context should have strategy metadata "git_worktree"
And the gwt sandbox worktree path should exist
Scenario: Creating a sandbox with empty plan_id raises ValueError
When a gwt sandbox is created with empty plan_id
Then a gwt ValueError should be raised with message "plan_id cannot be empty"
Scenario: Creating a sandbox with empty resource_id raises ValueError
When a gwt sandbox is prepared with empty resource_id
Then a gwt ValueError should be raised with message "resource_id cannot be empty"
Scenario: Creating a sandbox with empty original_path raises ValueError
When a gwt sandbox is prepared with empty original_path
Then a gwt ValueError should be raised with message "original_path cannot be empty"
Scenario: Creating a sandbox with non-positive timeout raises ValueError
When a gwt sandbox is prepared with zero timeout
Then a gwt ValueError should be raised with message "git_timeout must be positive"
Scenario: Creating a sandbox on a non-git directory raises SandboxCreationError
Given a gwt non-git directory
When a gwt sandbox is created on the non-git directory for plan "plan-001"
Then a gwt SandboxCreationError should be raised
# --- Path resolution ---
Scenario: Resolve a path in the worktree
When a gwt sandbox is created for plan "plan-001"
And the gwt path "src/main.py" is resolved
Then the gwt resolved path should be inside the worktree
And the gwt sandbox should be in the "active" state
Scenario: Path traversal is rejected
When a gwt sandbox is created for plan "plan-001"
And the gwt path "../etc/passwd" is resolved
Then a gwt ValueError should be raised with message "Path traversal not allowed"
Scenario: Resolving a path on a cleaned-up sandbox raises SandboxStateError
When a gwt sandbox is created for plan "plan-001"
And the gwt sandbox is cleaned up
And the gwt path "file.txt" is resolved on a cleaned-up sandbox
Then a gwt SandboxStateError should be raised
# --- Commit ---
Scenario: Commit with no changes produces empty result
When a gwt sandbox is created for plan "plan-001"
And the gwt sandbox is committed with message "empty commit"
Then the gwt commit result should indicate success
And the gwt commit result should have 0 changed files
Scenario: Commit with changes merges back to original branch
When a gwt sandbox is created for plan "plan-001"
And a gwt file "new_file.txt" is created in the worktree with content "hello"
And the gwt sandbox is committed with message "add new file"
Then the gwt commit result should indicate success
And the gwt commit result should have 1 added files
And the gwt file "new_file.txt" should exist in the original repo
Scenario: Commit with modified file reports changes
When a gwt sandbox is created for plan "plan-001"
And the gwt existing file "README.md" is modified in the worktree
And the gwt sandbox is committed with message "modify readme"
Then the gwt commit result should indicate success
And the gwt commit result should have 1 changed files
Scenario: Commit on a cleaned-up sandbox raises SandboxStateError
When a gwt sandbox is created for plan "plan-001"
And the gwt sandbox is cleaned up
And the gwt sandbox commit is attempted on cleaned-up sandbox
Then a gwt SandboxStateError should be raised
# --- Rollback ---
Scenario: Rollback discards worktree changes
When a gwt sandbox is created for plan "plan-001"
And a gwt file "temp.txt" is created in the worktree with content "temporary"
And the gwt path "temp.txt" is resolved
And the gwt sandbox is rolled back
Then the gwt sandbox should be in the "rolled_back" state
And the gwt file "temp.txt" should not exist in the worktree
Scenario: Rollback on a non-active sandbox raises SandboxStateError
When a gwt sandbox is created for plan "plan-001"
And the gwt sandbox rollback is attempted on created sandbox
Then a gwt SandboxStateError should be raised
# --- Cleanup ---
Scenario: Cleanup removes worktree and branch
When a gwt sandbox is created for plan "plan-001"
And the gwt sandbox is cleaned up
Then the gwt sandbox should be in the "cleaned_up" state
And the gwt sandbox worktree path should not exist
And the gwt sandbox branch should not exist
Scenario: Cleanup is idempotent
When a gwt sandbox is created for plan "plan-001"
And the gwt sandbox is cleaned up
And the gwt sandbox is cleaned up again
Then the gwt sandbox should be in the "cleaned_up" state
# --- Branch sanitisation ---
Scenario: Branch name is sanitised from plan ID
When a gwt sandbox is created for plan "plan with spaces!@#"
Then the gwt sandbox branch name should be safe for git
# --- Protocol properties ---
Scenario: Sandbox has a unique ULID identifier
When a gwt sandbox is instantiated
Then the gwt sandbox_id should be a valid ULID
And the gwt sandbox status should be "pending"
And the gwt sandbox context should be None