The Owner Is Off. Now What?
Laptops sleep. A Mac restarts for an update. The tailnet takes a few seconds to find a path. Any read across machines will eventually meet an owner that does not answer, and the receiving side has two honest options and one dishonest one.
- Fail with the cause. Say exactly what did not answer, on which route, in which phase.
- Serve the copy you have, and say it is a copy. The bytes were right the last time the owner confirmed them; whether that is good enough is not the cache's call.
- Serve the copy and say nothing. This is the dishonest one, and it is the default of more caches than anyone would like.
Delta does the first when it holds nothing, and the second when it holds something. It never does the third.
The Label
When the request to the owner raises Unavailable or Timeout and a cached entry exists, the engine serves the entry with X-Delta-Cache: stale and a second header, X-Delta-Stale, carrying the reason (URL-quoted, the first 200 characters): the same named cause the routing layer would have raised, for example that a connect to the peer over the tailnet timed out after eight seconds. The serve is logged as a warning and counted under stale_served. When nothing is cached, there is no copy to label: the error itself goes back, 503 for an unreachable owner or 504 for a timeout, with the same detail.
HTTP has a standard version of this idea (RFC 5861's stale-if-error), and it carries the same lesson: a stale answer is a legitimate answer only when it is announced. A connection that breaks in the middle of a small body is handled the same way: the engine reads a body of up to 4 MiB whole before sending a single header, so the caller receives the stale copy if one exists, or the named cause otherwise. A large body that was already streaming cannot be taken back; its connection ends short of the promised Content-Length, which the caller's client reads as an error, never as a complete file.
The Caller Decides What Stale Is Worth
Delta labels; it does not judge. Two of its readers make opposite decisions, and both are right:
| Caller | Policy on stale | Why |
|---|---|---|
| A thumbnail grid | Draw it | A picture from an hour ago is still the right picture |
| Soul Vault reading its canonical store | Refuse: raise CanonicalUnreachable, quoting X-Delta-Stale | A store that decides what is true may not decide from an old copy |
This is why the label matters more than the copy. If Delta silently served stale bytes, the vault reader could not tell, and would treat a snapshot as the present. Because the outcome is in a header, a policy that needs freshness can enforce it in one line, and a policy that tolerates staleness costs nothing.
Bounded Twice: Size, and the Disk Under It
A cache that grows forever eventually becomes the reason a Mac runs out of space. Delta's has two bounds. The first is a size cap, 20 GiB unless the Mac's host config sets cache_cap_gib. When the blobs exceed it, the least recently read entries are dropped until the total is back under 90% of the cap. The total is a scan of every blob row, so it is taken at most once a second, not after every stored thumbnail.
The second is a disk floor, 50 GiB unless cache_floor_gib says otherwise, and it is read the family way. macOS answers "how much room is left?" with two true numbers. statfs (what df and shutil.disk_usage report) counts only blocks free right now; System Settings and Finder add the purgeable space macOS frees on demand. On the workbench, on 2026-11-06, the first said 489.5 GB and the second 3.98 TB. A floor judged on the first would have called a Mac with almost four free terabytes nearly full. So the cache judges by the System Settings figure (through the family's kit_disk_space reader), plus a small hard stop on free-right-now space (2 GiB) as a last line, since nobody has measured that macOS purges fast enough for a writer that never pauses.
Under the floor, the cache does not fail anything. It stops storing new entries and serves straight through, logs the change once (cache paused (no new entries), and later resumed), counts each skipped entry under skipped_disk, and shows storing: false with the figures in health. Reads keep working; only the remembering pauses.