18 KiB
adr_number, title, status_history, tier, authors, superseded_by, related_adrs, acceptance
| adr_number | title | status_history | tier | authors | superseded_by | related_adrs | acceptance | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 36 | Resource DAG Operational Semantics |
|
3 |
|
null |
|
|
Context
ADR-008 defines the resource DAG's structural rules (acyclicity, type compatibility, unique links, existence constraints) and its role in resource discovery and sandbox ordering. The specification (lines 22660–22911) elaborates the 24 built-in types, virtual/physical equivalence linking, and divergence detection. However, neither document articulates a unified theory of what the DAG is for at runtime — what operational questions it answers, what algorithms it supports, and how its structure drives sandbox creation, tool routing, access control propagation, and change coordination across heterogeneous mechanisms.
This ADR defines the operational semantics of the resource DAG: the purpose-driven theory that connects DAG structure to runtime behavior.
Decision Drivers
- The DAG's structural rules (ADR-008) exist but no unified theory connects DAG structure to runtime behavior across subsystems (sandbox, tool routing, access control, auto-discovery)
- Sandbox boundaries must be explicit and queryable — a file cannot be sandboxed in isolation; its containing repository or directory must define the boundary
- Tool reachability, read/write routing, and access control propagation all traverse the DAG differently and need formalized semantics
- Lifecycle operations (creation, commit, rollback, cleanup) require correct topological orderings with direction varying per operation type
- Change propagation through virtual resource equivalence classes must terminate deterministically and not cascade unboundedly
Decision
The resource DAG serves as the topology of the system's operational world. Its structure supports ten distinct operational purposes, organized into three categories: reachability (how tools connect to resources), isolation (how sandboxing and checkpointing map to DAG positions), and coordination (how changes propagate and how equivalent resources stay synchronized). This ADR defines the sandbox boundary algebra and dependency ordering protocols that emerge from these purposes.
Design
The Ten Purposes of the Resource DAG
The DAG answers five fundamental questions, each served by specific operational purposes:
| Question | DAG Feature | Purposes |
|---|---|---|
| What is this? | Virtual resource identity (hub node) | Equivalence classes |
| Where does it live? | Physical manifestations and containment hierarchy | Reachability, auto-discovery |
| How do I reach it? | Tool binding → containment → access projection | Tool reachability, access projection, read/write routing |
| How do I safely change it? | Sandbox boundaries and coordination policies | Sandbox boundary algebra, cross-mechanism coordination |
| What else is affected? | Descendant invalidation, ancestor propagation, sibling sync | Change propagation, access control propagation, dependency ordering |
Purpose 1: Tool Reachability
Knowing which tools can reach a particular resource for reading or writing it. A tool declares resource slots specifying what types it binds to. Through the DAG's containment hierarchy, a tool bound to a git-checkout can transitively reach every fs-directory and fs-file descendant within that checkout. The DAG supports both forward reachability (tool → bound resource → descendants) and inverse reachability (resource → ancestors → tools with compatible bindings). See ADR-037 for the full reachability model.
Purpose 2: Equivalence and Alternative Paths
Knowing which physical resources are equivalent so the system can find alternative tools and paths for reaching the same logical resource. Virtual resources are identity hubs that link physical resources sharing the same content or structural identity. When one path to a resource is unavailable (e.g., an MCP server is down), the system uses the DAG to find alternative physical manifestations of the same virtual resource, each potentially reachable through different tools. See the specification's Physical vs Virtual Resources section for the 9 equivalence types.
Purpose 3: Read/Write Routing
Routing reads and writes through different physical manifestations of the same virtual resource. An LSP server provides semantically rich reads (type info, references, diagnostics) while a git-checkout provides sandbox-tracked writes. The DAG, combined with equivalence classes, enables the system to route reads through the richest available path and writes through the canonical sandbox-tracked path. See ADR-037 for read/write routing.
Purpose 4: Sandbox Boundary Algebra
Understanding at what level in the DAG sandboxing and checkpointing take place. Not every resource in the DAG is independently sandboxable. A file cannot be sandboxed in isolation — its containing directory (copy_on_write) or git repository (git_worktree) must be sandboxed, and the file inherits that sandbox. The DAG defines sandbox boundaries: specific nodes at which sandboxing is physically implementable. All descendants of a sandbox boundary share that boundary's sandbox instance. See the Sandbox Boundary Algebra section below.
Purpose 5: Cross-Mechanism Sandbox Coordination
Coordinating sandbox and checkpoint operations across different physical resources that share a virtual identity. When the same logical file exists in a git-checkout sandbox and a container bind-mount, changes through one path must be accounted for in the other. The DAG's equivalence structure, combined with coherence properties on the physical-to-virtual edges, provides the information needed to synchronize or coordinate across different sandbox mechanisms. See ADR-038 for the coordination protocol.
Purpose 6: Dependency Ordering
Determining the correct order for lifecycle operations. The DAG provides topological orderings for sandbox lifecycle operations where the traversal direction differs per operation:
| Operation | Traversal | Rationale |
|---|---|---|
| Sandbox creation | Top-down (parent before child) | A parent resource must be sandboxed before its children can be accessed within the sandbox |
| Sandbox commit/apply | Bottom-up (child before parent) | Child changes should be finalized before the parent incorporates them (e.g., file changes committed before directory snapshot) |
| Sandbox rollback | Top-down (parent before child) | Rolling back a parent implicitly rolls back children within its sandbox domain |
| Sandbox cleanup | Bottom-up (child before parent) | Clean up child sandboxes before removing the parent, to avoid dangling references |
| Auto-discovery | Top-down (parent before child) | Discover containers before their contents, directories before their files |
| Auto-discovery refresh | Bottom-up then top-down | Re-evaluate changed children, then cascade to parent identity re-evaluation |
Purpose 7: Change Propagation and Invalidation
Determining what else is affected when a resource changes. When a tool writes to a file:
- Descendant impact: Rarely applicable for files, but a directory change may affect files (e.g., permission changes).
- Ancestor impact: The file's parent directory has changed contents. The git-checkout has uncommitted changes. These ancestors need status updates.
- Equivalence sibling impact: If the file has a virtual parent, other physical siblings may need cache invalidation (LSP server), content refresh, or conflict detection.
- Cascading identity re-evaluation: A file content change may break its link to its
filevirtual parent. A directory containing that file may break itsdirectoryvirtual link. The DAG provides the traversal path for this cascade (see specification: Divergence Detection / Cascading Divergence).
Purpose 8: Access Control Propagation
Propagating read-only and capability constraints through the containment hierarchy. A resource marked read_only at the project level propagates that constraint to all descendants. A git-checkout marked read-only means all files within it are read-only, regardless of their individual capabilities. The query "is this resource writable, considering all its ancestors' constraints?" requires walking up the containment edges.
More precisely, the effective writability of a resource is:
effective_writable(r) = r.capabilities.writable
AND all(p.capabilities.writable for p in containment_ancestors(r))
AND NOT project_read_only(r)
Purpose 9: Auto-Discovery Scope
Determining the scope and cascade of resource auto-discovery. The DAG defines which resources are parents of which, and auto-discovery rules are configured per resource type. When a git-checkout is registered, auto-discovery cascades down: discover the git child and fs-directory child, then the git handler discovers branches/tags/commits/trees, and the fs-directory handler discovers subdirectories and files. The DAG is the traversal structure for both initial discovery and incremental refresh.
Purpose 10: Tool Capability Inference
Inferring tool capabilities from DAG position. A tool bound at the git-checkout level inherits the ability to transitively write to any file within the checkout. But a tool bound only to a specific fs-file cannot create directories or modify git metadata. The DAG position constrains and infers tool capabilities beyond what the tool explicitly declares — the effective write scope of a tool is bounded by the sandbox domain of its bound resource.
Sandbox Boundary Algebra
Definitions
Sandbox boundary: A node in the DAG at which sandboxing is physically implementable. Formally: resource b is a sandbox boundary if b.capabilities.sandboxable == true and b.sandbox_strategy != none.
Sandbox domain: The set of all descendants of a sandbox boundary (along contains edges) that do not have their own intervening sandbox boundary. If a descendant d of boundary b is itself a sandbox boundary, then d and its descendants form a separate sandbox domain, not part of b's domain.
sandbox_boundary(r): A function that returns the nearest ancestor sandbox boundary of resource r (inclusive — if r is itself a boundary, returns r). If no sandbox boundary exists in r's ancestor chain, r is unsandboxable and sandbox_boundary(r) = None.
sandbox_domain(b): A function that returns the set {r : sandbox_boundary(r) == b} — all resources whose nearest sandbox boundary is b.
Properties
-
Partition: Every sandboxable resource belongs to exactly one sandbox domain. The sandbox domains partition the set of all sandboxable resources into disjoint groups.
-
Shared sandbox instance: All resources within the same sandbox domain share a single sandbox instance during plan execution. The
SandboxManagerkeys sandboxes by(plan_id, sandbox_boundary_id), not by individual resource ID. -
Inheritance: Child resources inherit their parent's sandbox unless they define their own boundary. This is already described in the specification ("The sandbox strategy is inherited by child resources from their parent unless the child type defines its own"), but the sandbox boundary algebra makes the inheritance structure queryable and explicit.
-
Cross-equivalence boundaries: A virtual resource's physical manifestations may reside in different sandbox domains. For example, a file's
fs-filemanifestation is in thegit-checkout's sandbox domain (git_worktree), while itscontainer-mount-filemanifestation is in thecontainer-instance's sandbox domain (snapshot), and itslsp-documentmanifestation has no sandbox at all (none). The set of sandbox boundaries for a virtual resource is:{sandbox_boundary(p) for p in physical_manifestations(v)}.
Checkpoint Boundaries
Checkpoint boundary: A node in the DAG at which state capture (checkpointing) is physically implementable. Checkpoint boundaries may coincide with sandbox boundaries or be finer-grained.
For most resource types, the checkpoint boundary is the same as the sandbox boundary (e.g., a git commit captures the entire worktree). For databases, checkpoint boundaries can be finer — a savepoint within a transaction captures a subset of changes within the transaction sandbox.
The tool's declared checkpoint_scope must be compatible with the checkpoint boundary's granularity:
| Tool checkpoint_scope | Compatible checkpoint boundaries |
|---|---|
file |
git-checkout (via git commit), fs-directory (via filesystem snapshot) |
transaction |
Database resource (via savepoint) |
commit |
git-checkout (via git commit) |
snapshot |
Any resource with snapshot capability (containers, filesystems, databases) |
DAG Edge Types
The resource DAG uses three types of directed edges:
| Edge Type | Semantics | Traversal in queries |
|---|---|---|
contains |
Parent physically contains or structurally owns the child. The primary hierarchy. | Sandbox domain computation, reachability, auto-discovery, access projection, dependency ordering |
references |
Soft dependency — the parent depends on or uses the child, but does not contain it. | Ordering constraints, capability discovery (e.g., lsp-server references executable) |
| Physical-to-virtual (child of virtual parent) | Physical resource is a manifestation of the virtual identity. | Equivalence class queries, read/write routing, cross-mechanism coordination, divergence detection |
Note: The physical-to-virtual relationship uses the standard parent-child edge with the virtual resource as parent. The edge direction means "this virtual identity has these physical manifestations as children." This is consistent with the existing DAG structure — contains edges from virtual parents to physical children carry the semantics "this identity is realized by these physical resources."
Constraints
- Sandbox boundary computation must be O(depth) where depth is the longest path from the resource to the DAG root. In practice, DAGs are shallow (typically 3–6 levels), so this is effectively O(1).
- Change propagation must not trigger unbounded cascades. Divergence re-evaluation stops when a virtual resource's identity is stable (no further changes detected in its physical children).
- Access control propagation is read-only and stateless —
effective_writable(r)is computed fresh each time, not cached, to avoid stale permission data. - Dependency ordering for lifecycle operations must produce a valid topological sort. If the DAG has multiple connected components, each component is ordered independently.
Consequences
Positive
- A unified theory connecting DAG structure to runtime behavior, replacing implicit knowledge scattered across multiple subsystems.
- Sandbox boundary algebra makes sandbox sharing explicit and queryable, instead of implicit in the SandboxManager implementation.
- Dependency ordering protocols prevent incorrect lifecycle sequencing (e.g., cleaning up a parent before its children).
- The ten purposes provide a clear framework for evaluating whether a proposed DAG change (new edge type, new resource type) is well-motivated.
Negative
- The operational semantics add conceptual complexity beyond the structural rules in ADR-008. Implementers must understand both the structural and operational layers.
- Sandbox boundary computation introduces a new query pattern that the current repository layer does not directly support (ancestor walks along
containsedges).
Risks
- The sandbox boundary algebra assumes DAGs are shallow. Deeply nested resource hierarchies (unlikely in practice but possible with custom types) could make boundary computation expensive.
- Change propagation cascades through virtual resource re-evaluation could become expensive if a single virtual resource has many physical manifestations across many repositories.
Alternatives Considered
Implicit operational semantics (status quo) — Each subsystem (SandboxManager, BindingResolutionService, auto-discovery) implements its own understanding of the DAG without a shared theory. This works for simple cases but leads to inconsistent behavior as the system grows (e.g., SandboxManager and tool routing may disagree on what "reachable" means).
Flat resource model without DAG — Resources exist independently without hierarchy. Simpler but cannot support transitive reachability, sandbox domain sharing, or automatic change propagation.
Compliance
- Sandbox boundary tests: Unit tests verify
sandbox_boundary(r)andsandbox_domain(b)for all built-in resource type hierarchies (git, filesystem, container, LSP). - Dependency ordering tests: Tests verify correct topological ordering for creation, commit, rollback, and cleanup operations.
- Change propagation tests: Tests verify that changes to a file trigger ancestor status updates and virtual resource re-evaluation, with cascade termination.
- Access control propagation tests: Tests verify that
effective_writable(r)correctly aggregates constraints from all ancestors and project-level read-only flags.