Skip to content

Reference-path correctness

A mounted AWC workspace is a real Linux filesystem, not a simulation. A build or an agent that walks the tree sees a directory served by the kernel. The choice is deliberate, and it is the reason standard tools behave correctly inside a workspace (spec §6.9, §14.1).

The kernel resolves paths

AWC mounts a workspace as a kernel OverlayFS. The lower layer is the shared, read-only FUSE filesystem at /var/lib/awc/basefs. The upper layer is a private writable directory. The merged view is what the agent sees (spec §8.4, §11.1, §11.3):

merged view = immutable lower snapshot + private local upper

Path resolution is the kernel's job, not the daemon's. The Linux VFS walks a path one component at a time. For each component it sends a single lookup to the filesystem that owns the parent. It handles ., .., trailing slashes, and symlink-following rules itself. AWC does not reimplement any of that.

This matters because path resolution is full of edge cases. Symlink loops, .. across mount boundaries, O_NOFOLLOW, and magic links all have defined Linux semantics. A custom virtual filesystem that took a whole path string and resolved it itself would have to reimplement every rule, and would get some of them wrong. Routing through the VFS inherits the kernel's implementation, which tools already depend on.

The lower layer is read-only and content-addressed

Every snapshot lives at a digest-qualified path (spec §8.4):

/var/lib/awc/basefs/tenants/<tenant-id>/snapshots/<root-digest>/

Two properties follow from that address.

The content cannot change. A snapshot is an immutable Merkle tree (spec §6.1, §9.7). A read is strongly snapshot-consistent: every path resolves against one root digest, and the bytes cannot shift after publication (spec §14.1). The daemon returns generous entry and attribute TTLs to the kernel, because the answer is fixed for the life of the mount (spec §15.7).

The daemon answers one component at a time. The FUSE lookup takes a parent inode and a single name. It never parses a path string, and it rejects a name that contains / or NUL. To resolve a name inside a snapshot, it walks the immutable TREE manifests (spec §10.1). Symlinks are data, not directions: the stored target is returned by readlink, and the kernel decides whether to follow it (spec §9.6, §10.1).

Writes are isolated to the upper layer

The basefs rejects every mutation. The FUSE layer replies EROFS to write, create, mkdir, unlink, rename, setattr, setxattr, and every other mutator. open refuses anything that is not O_RDONLY. Reported permission bits have the write bits stripped, so a stat agrees with the read-only truth. The spec requires exactly this: unsupported writes on the base filesystem MUST return EROFS, and all mutations occur in the private upper through OverlayFS (spec §8.4, §11.1).

The overlay does the write work. Copy-up, whiteouts, and directory opacity are OverlayFS semantics, interpreted by the checkpoint builder (spec §11.7). A tool that writes a file is talking to a normal writable filesystem. It does not know that some bytes came from an immutable snapshot underneath.

The caches must agree with this path

Spec §6.9 defines the invariant. Every optimized read path — the page cache, the manifest cache, prefetch, singleflight — MUST be observationally equivalent to resolving the path from the immutable snapshot manifests and fetching the verified objects.

The design makes the invariant easier to keep. The kernel drives resolution, and the daemon answers each lookup from the same manifests a cold, unoptimized read would walk. A cache that served a different answer for the same path would be visible to any tool that re-stat the file. There is no second, private resolution path to drift out of agreement. For how those caches are layered, see overlay, cache, and singleflight.

Safe traversal on ingest

Path safety runs in the other direction too, when content is ingested. Import, checkpoint, and export open files relative to directory descriptors, not by concatenating paths. They prefer openat2 with RESOLVE_BENEATH and RESOLVE_NO_MAGICLINKS, and fall back to openat with O_NOFOLLOW where the kernel lacks openat2 (spec §8.6, §15.10). Symlinks are reported, never followed (spec §9.6). Containment is never left to the caller: where openat2 exists the kernel enforces the authorized root, and on the openat fallback the crate rechecks each component itself.

Where the evidence stops

The kernel-VFS resolution just described is a local property of one host. The spec also requires that a read-only mount of the same snapshot on different hosts resolves to identical logical bytes (spec §14.1). That cross-host equivalence is specified, not yet measured. The AWC pilot is a single host, and the recorded micro-measurements are loopback-only.

The read-only basefs is exposed to root and to the awcd mount owner through FUSE AllowRoot. That grant exists so the kernel OverlayFS can resolve its lowerdir with the credentials it needs. Without it the overlay mount fails with EACCES. The grant does not widen write access, because the mutators still reply EROFS.

Spec §6.9 is a property the optimized paths must maintain, not a claim the code can prove on its own. It is checked by the digest-correctness and tool-compatibility gates, which compare mounted behaviour against the verified objects (spec §20.2).