Files
cleveragents-core/features/sandbox_boundary_algebra.feature
T
freemo 5935940276
CI / lint (pull_request) Successful in 16s
CI / typecheck (pull_request) Successful in 31s
CI / security (pull_request) Successful in 29s
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m21s
CI / quality (pull_request) Successful in 2m33s
CI / unit_tests (pull_request) Successful in 3m10s
CI / integration_tests (pull_request) Successful in 5m17s
CI / coverage (pull_request) Successful in 4m25s
CI / docker (pull_request) Successful in 40s
CI / lint (push) Successful in 12s
CI / typecheck (push) Successful in 31s
CI / quality (push) Successful in 15s
CI / security (push) Successful in 32s
CI / build (push) Successful in 21s
CI / integration_tests (push) Successful in 3m6s
CI / unit_tests (push) Successful in 3m30s
CI / benchmark-regression (push) Has been skipped
CI / docker (push) Successful in 1m43s
CI / coverage (push) Successful in 5m21s
CI / benchmark-regression (pull_request) Successful in 31m11s
CI / benchmark-publish (push) Successful in 17m24s
feat(sandbox): implement sandbox boundary algebra and domain computation
Implement sandbox_boundary(r) function that walks up containment edges
in the resource DAG to the nearest sandboxable ancestor, enabling
resources sharing a boundary to share one sandbox instance.

Changes:
- Add boundary.py: is_sandbox_boundary(), sandbox_boundary(),
  compute_sandbox_domains(), BoundaryCache (thread-safe, per-execution)
- Update SandboxManager: resolve_sandbox_key() and
  get_or_create_sandbox_for_resource() key by (plan_id, boundary_id)
  instead of (plan_id, resource_id); boundary cache lifecycle methods
- Define "sandboxable" via ResourceCapabilities.sandboxable + non-none
  sandbox_strategy as per specification section 24659-24674
- Export new symbols from sandbox __init__.py
- Add vulture whitelist entries for new public API

Tests:
- 26 Behave BDD scenarios (features/sandbox_boundary_algebra.feature)
- 5 Robot Framework integration tests (robot/sandbox_boundary_algebra.robot)
- ASV benchmarks for boundary walk, domain grouping, and cache performance

ISSUES CLOSED: #548
2026-03-04 15:59:15 +00:00

186 lines
9.1 KiB
Gherkin

Feature: Sandbox Boundary Algebra
As a plan execution engine
I want to compute sandbox boundaries by walking the resource DAG
So that resources sharing a boundary share one sandbox instance
Background:
Given a resource registry for boundary algebra tests
# -- sandbox_boundary(r) basic resolution --
Scenario: A resource that is itself a sandbox boundary returns itself
Given a sandboxable resource "checkout-01" with strategy "git_worktree"
When I compute the sandbox boundary for "checkout-01"
Then the boundary should be "checkout-01"
Scenario: A child resource resolves to its sandboxable parent
Given a sandboxable resource "checkout-01" with strategy "git_worktree"
And a non-sandboxable resource "file-01" with parent "checkout-01"
When I compute the sandbox boundary for "file-01"
Then the boundary should be "checkout-01"
Scenario: A deeply nested resource resolves to a distant boundary
Given a sandboxable resource "checkout-01" with strategy "git_worktree"
And a non-sandboxable resource "dir-01" with parent "checkout-01"
And a non-sandboxable resource "subdir-01" with parent "dir-01"
And a non-sandboxable resource "file-deep" with parent "subdir-01"
When I compute the sandbox boundary for "file-deep"
Then the boundary should be "checkout-01"
Scenario: A resource with no sandboxable ancestor returns None
Given a non-sandboxable resource "orphan-01" with no parents
When I compute the sandbox boundary for "orphan-01"
Then the boundary should be None
Scenario: A resource whose parent is not in the registry returns None
Given a non-sandboxable resource "dangling-01" with missing parent "ghost-99"
When I compute the sandbox boundary for "dangling-01"
Then the boundary should be None
# -- is_sandbox_boundary checks --
Scenario: A resource with sandboxable capability and non-none strategy is a boundary
Given a sandboxable resource "boundary-res" with strategy "copy_on_write"
When I check if "boundary-res" is a sandbox boundary
Then it should be a sandbox boundary
Scenario: A resource with sandboxable capability but none strategy is not a boundary
Given a resource "non-boundary" with sandboxable true and strategy "none"
When I check if "non-boundary" is a sandbox boundary
Then it should not be a sandbox boundary
Scenario: A resource with no sandboxable capability is not a boundary
Given a non-sandboxable resource "no-cap" with no parents
When I check if "no-cap" is a sandbox boundary
Then it should not be a sandbox boundary
Scenario: Checking is_sandbox_boundary with None raises ValueError
When I check is_sandbox_boundary with None resource
Then the error should be a ValueError with text "resource cannot be None"
# -- domain grouping --
Scenario: Resources with the same boundary are grouped into one domain
Given a sandboxable resource "checkout-01" with strategy "git_worktree"
And a non-sandboxable resource "file-a" with parent "checkout-01"
And a non-sandboxable resource "file-b" with parent "checkout-01"
When I compute sandbox domains for "file-a" and "file-b"
Then both resources should be in the domain of "checkout-01"
Scenario: Multiple boundaries produce separate domains
Given a sandboxable resource "checkout-01" with strategy "git_worktree"
And a sandboxable resource "dir-root" with strategy "copy_on_write"
And a non-sandboxable resource "file-git" with parent "checkout-01"
And a non-sandboxable resource "file-fs" with parent "dir-root"
When I compute sandbox domains for "file-git" and "file-fs"
Then "file-git" should be in the domain of "checkout-01"
And "file-fs" should be in the domain of "dir-root"
Scenario: Unsandboxable resources are grouped under the special key
Given a non-sandboxable resource "orphan-a" with no parents
And a non-sandboxable resource "orphan-b" with no parents
When I compute sandbox domains for "orphan-a" and "orphan-b"
Then both resources should be in the unsandboxable domain
# -- boundary caching --
Scenario: Boundary results are cached across lookups
Given a sandboxable resource "checkout-01" with strategy "git_worktree"
And a non-sandboxable resource "file-cached" with parent "checkout-01"
And a fresh boundary cache
When I look up the boundary for "file-cached" via the cache
And I look up the boundary for "file-cached" via the cache again
Then the cache should contain 1 entry
And the cached boundary for "file-cached" should be "checkout-01"
Scenario: Cache can be cleared
Given a sandboxable resource "checkout-01" with strategy "git_worktree"
And a non-sandboxable resource "file-c" with parent "checkout-01"
And a fresh boundary cache
When I look up the boundary for "file-c" via the cache
And I clear the boundary cache
Then the cache should contain 0 entries
Scenario: Cache returns None for unsandboxable resources
Given a non-sandboxable resource "orphan-cached" with no parents
And a fresh boundary cache
When I look up the boundary for "orphan-cached" via the cache
Then the cached boundary result should be None
And the cache should contain 1 entry
# -- SandboxManager boundary-aware keying --
Scenario: Manager resolves sandbox key via boundary algebra
Given a located sandboxable resource "checkout-01" strategy "git_worktree" location "/tmp/repo"
And a non-sandboxable resource "file-01" with parent "checkout-01"
And a sandbox factory instance
And a sandbox manager with the factory
When I resolve the sandbox key for plan "plan-001" resource "file-01"
Then the sandbox key should be "checkout-01"
Scenario: Manager creates sandbox keyed by boundary ID
Given a located sandboxable resource "checkout-01" strategy "git_worktree" location "/tmp/repo"
And a non-sandboxable resource "file-01" with parent "checkout-01"
And a non-sandboxable resource "file-02" with parent "checkout-01"
And a mock sandbox factory
And a sandbox manager with the mock factory
When I get or create a boundary sandbox for plan "plan-001" resource "file-01"
And I get or create a boundary sandbox for plan "plan-001" resource "file-02"
Then both resources should share the same sandbox instance
Scenario: Manager raises NoSandboxBoundaryError for orphan resource
Given a non-sandboxable resource "orphan-mgr" with no parents
And a sandbox factory instance
And a sandbox manager with the factory
When I try to resolve the sandbox key for plan "plan-001" resource "orphan-mgr"
Then a NoSandboxBoundaryError should be raised
Scenario: Manager boundary cache is cleared on demand
Given a located sandboxable resource "checkout-01" strategy "git_worktree" location "/tmp/repo"
And a non-sandboxable resource "file-01" with parent "checkout-01"
And a sandbox factory instance
And a sandbox manager with the factory
When I resolve the sandbox key for plan "plan-001" resource "file-01"
Then the manager boundary cache should have 1 entry
When I clear the manager boundary cache
Then the manager boundary cache should have 0 entries
# -- argument validation --
Scenario: sandbox_boundary with None resource raises ValueError
When I call sandbox_boundary with None resource
Then the error should be a ValueError with text "resource cannot be None"
Scenario: sandbox_boundary with None registry raises ValueError
Given a non-sandboxable resource "dummy" with no parents
When I call sandbox_boundary with None registry for "dummy"
Then the error should be a ValueError with text "resource_registry cannot be None"
Scenario: compute_sandbox_domains with None resources raises ValueError
When I call compute_sandbox_domains with None resources
Then the error should be a ValueError with text "resources cannot be None"
Scenario: compute_sandbox_domains with None registry raises ValueError
Given a non-sandboxable resource "dummy" with no parents
When I call compute_sandbox_domains with None registry
Then the error should be a ValueError with text "resource_registry cannot be None"
Scenario: BoundaryCache with None resource raises ValueError
Given a fresh boundary cache
When I call boundary cache get_boundary with None resource
Then the error should be a ValueError with text "resource cannot be None"
Scenario: BoundaryCache with None registry raises ValueError
Given a non-sandboxable resource "dummy" with no parents
And a fresh boundary cache
When I call boundary cache get_boundary with None registry for "dummy"
Then the error should be a ValueError with text "resource_registry cannot be None"
Scenario: Boundary walk skips already-visited parent IDs
Given a sandboxable resource "checkout-01" with strategy "git_worktree"
And a non-sandboxable resource "dir-a" with parent "checkout-01"
And a non-sandboxable resource "dir-b" with parent "checkout-01"
And a non-sandboxable resource "file-shared" with parents "dir-a" and "dir-b"
When I compute the sandbox boundary for "file-shared"
Then the boundary should be "checkout-01"