Inside the disk
Inodes live in a SQLite table and file contents in 64 KiB chunks. The
inode row carries the POSIX surface — kind, size, mtime, mode, uid,
gid — and a content id; chunks are stored against the content id, not
the path, which is what makes atomic replacement possible. An LRU keeps
up to 512 hot chunks (32 MiB) in memory without making memory the source
of truth, and it shrinks itself under memory pressure.
Every path also carries a revision watermark for its subtree. One global
mutation clock ticks per write, and the written path plus all of its
ancestors are stamped with the new value — so revision('src') answers
“has anything under src changed” in one lookup, and an unrelated write
doesn’t invalidate the cache.
The write pipeline
Section titled “The write pipeline”Facets never mutate files directly. Every write streams to the
supervisor as length-prefixed, checksummed frames, and the stream is
governed by a weighted credit pool: 8 MiB of retained payload bytes,
shared across every writer in the session. A sender acquires credit
sized to each 64 KiB chunk before the supervisor reads its payload;
with no credit, the RPC body itself stops being read, so backpressure
reaches the sender through the stream, not through a convention. Waiters
are served in FIFO order, so a 52,500-file npm install and an
84,000-file checkout can run in parallel inside 128 MiB isolates without
either starving the other.
Publication is path-atomic. A bulk write stages its chunks into a fresh content generation under bounded transactions (at most 1 MiB of blob bytes per transaction), then a single SQLite transaction swaps the inode’s content id to the staged generation and marks the old one for collection. A crash mid-stream leaves a committed prefix of whole files, not a partially-written file or a partially-published tree.
Durability boundaries are fsync and process exit: every write returns
only after its SQLite transaction commits, and hibernation cannot lose a
committed write.
The read side
Section titled “The read side”Runtimes use RuntimeFsBridge for stat, range reads and writes, truncate,
directory and symlink operations, revision(path), and events. The bridge
is stateless, credentialed per process, and survives supervisor
hibernation. Range operations touch only the chunks in range — appending
a byte to a large file never rewrites the file.
Not every read goes to the live bridge yet. Node’s synchronous fs runs
against a bounded snapshot of the working tree — capped at 24 MiB and
4,000 files, node_modules excluded — with a local write cache, because
synchronous calls cannot await an RPC. Node’s async fs uses the live
bridge. Fresh git clones carry a metadata-only closed-world overlay
across invocation boundaries so checkout phases can resume without
re-reading the world. Python, Ruby, bash, and WASI one-shots still use a
snapshot plus diff flush; moving them to the live bridge is
active work.
The coherence rule: every read sees the latest committed revision or its own unflushed write. Conflicts fail loudly rather than overwriting data silently.