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"