**Note** from Bead: Decentralized Cache Management · [canonical source](https://redfish.acequia.io/guerin/.agents/4456cd24-010a-41e9-a14c-a2d1086893bd/2026-06-14/notes/00-cache-coherence-model.md) · session 2026-06-14 · discussion: Talk: Decentralized Cache Management
*Bead `4456cd24` · opened 2026-06-14.* > **Reframed 2026-06-14:** Stephen scoped this bead as *"caches as shared state — a decentralized Firebase-like state-management substrate."* That makes **[01-cache-as-state-substrate.md](01-cache-as-state-substrate.md) the lead note**; this note becomes the **machinery layer** beneath it — the same three axes below (identity, invalidation, authority) are the mechanisms that substrate runs on. Read 01 for the "what for," this for the "how."
## The problem in one sentence The ecology treats the **namespace as the primitive** — many physical nodes serve one logical URI ([distributed-origin-architecture](https://redfish.acequia.io/guerin/.agents/874fce5b-9c8b-4b23-b2ed-429148c6c4b7/2026-04-23/notes/)) — so **every cache in the stack is a replica of the same logical resource**, and replicas without a coherence protocol drift silently: a reader walks the canonical URI and gets a stale copy with no signal that it is stale. This is the classic cache-coherence problem (the kind multiprocessor caches and CDNs solve), but here there is **no central cache authority** to issue invalidations — coherence has to be reached peer-to-peer across a mesh of nodes and browsers.
## The caching layers in this ecology (inventory) Walking a request from the canonical URI outward to a reader, the copies live at: | Layer | Where | Fills via | Validates via | Evicts via | Notes | |---|---|---|---|---|---| | **Origin store** | the acequia WebDAV stores (multiple endpoints mirror one namespace) | `PUT` / `/bead-sync` (additive) | PROPFIND + Last-Modified/ETag | manual / sync | the "source of truth" — but there are *several*, each a replica | | **OS-node origin** | home Caddy edge → Node origin (`7049c694`, `82bd6fa4`) | upstream GET | conditional GET | TTL / restart | `wiki.harvardviz.live`, `stephenguerin.live` | | **Tunnel/edge** | Cloudflare tunnel edges | upstream GET | Cache-Control/ETag | purge API / TTL | classic CDN cache | | **HTTP cache** | browser + intermediary HTTP caches | response | Cache-Control, ETag, `If-None-Match` | max-age / no-store | the version-query-string pattern busts this one | | **Service-worker Cache API** | per-origin, behind a SW (`fe7fbaf5` liveness ladder, `browser-hosting`) | `cache.put` on fetch | SW revalidation logic (app-defined) | SW `cache.delete` / version bump | **stale-while-revalidate lives here**; cross-origin fill is duty-4 of the orchestrator | | **OPFS / IndexedDB** | browser-local durable storage | app writes | app-defined | app-defined | the browser-as-CDN store; longest-lived browser copy | Two facts make this hard: 1. **The same logical path appears in many of these at once**, on many devices. 2. **The lower layers (SW, OPFS) are the least observable from the origin** — the origin cannot enumerate or directly invalidate them. Whatever invalidation exists must be *pulled* or *signaled*, not *pushed* by central command.
## Coherence model (draft sketch) Three orthogonal axes; pick a point on each per resource class (they need not be uniform across the namespace — every-constraint-is-soft). ### 1. Identity / versioning — *what makes two copies "the same"?* - **Mutable URI + validator** (ETag / Last-Modified): copies revalidate with `If-None-Match`; cheap, but needs a round-trip and a reachable validator. - **Content-addressed** (hash in the name/path): a copy is *immutable*, so it is never stale; "update" = new address. Eliminates invalidation at the cost of a name-resolution / mutable-pointer layer on top. Strong fit for the namespace-as-primitive frame and for OPFS pinning. - **Version query string** (the existing `query-string-version-cache-busting-pattern`): a poor-man's content address for the HTTP layer. ### 2. Invalidation — *how does an update reach the replicas, with no central authority?* Frame invalidation as an **apoptotic signal** (apoptosis-vs-necrosis), not a central purge: the authoritative writer of a path emits a "this version is dead" signal that propagates peer-to-peer (paths-as-event-bus: PUT/observe on an `invalidate` path; or the dock "hey you, it's me" protocol). Replicas that observe it evict or revalidate; replicas that never hear it fall back to **TTL** (a self-apoptosis timer) so no copy is necrotic forever. Lease/TTL is the floor; the signal is the fast path. ### 3. Authority / leadership — *which replica is canonical right now?* With several origin endpoints mirroring one namespace, "the source of truth" is itself replicated. A **lease / leader-election** per path (named in the orchestrator's duty-3 and the hosting runbook `7049c694`) decides which endpoint is authoritative for writes at a given moment; others are read-replicas that revalidate against the leader. This is where decentralized cache management meets distributed consensus. ### Consistency target Almost certainly **eventual consistency** with bounded staleness (TTL ceiling) rather than strong consistency — the mesh has partitions (NAT, offline browsers, sleeping nodes) and the ecology already tolerates them. **stale-while-revalidate** is the natural read policy: serve the cached copy immediately, revalidate in the background, upgrade on the next read.
## Relationship to adjacent beads (boundary) - **`4c6470f9` bead-orchestrator** — its duty-3 (smart multi-endpoint sync + leader election) and duty-4 (SW cache → CORS-origin bridge) are *concrete instances* of this model. **Boundary proposal:** the general coherence model lives here; the running sync/election/bridge mechanism lives there (or in a child bead it hatches). This boundary is open question Q5. - **`7049c694` / `82bd6fa4`** — the OS-node origin + router are edge-cache layers; any cache headers / TTLs they set are policy instances of axis-2. - **`fe7fbaf5` bead-as-webdav-handler** — its liveness ladder (static → SW → OS animator) is exactly the SW-cache layer; its sync-verb vs async-dock lanes map onto fill-vs-signal.
## Open questions (gated on Stephen — nothing below the model is reified until bound) 1. **Scope / framing.** Is "decentralized cache management" the *general coherence model* (this note's bet), or did you have a specific layer/pain in mind — e.g. SW caches serving stale cross-origin content, or multi-endpoint origin divergence after `/bead-sync`? 2. **Consistency target.** Confirm eventual-consistency + bounded-staleness + stale-while-revalidate as the default, or is there a class of resource that needs strong consistency? 3. **Identity.** Do we lean into **content-addressing** for cacheable resources (immutable copies, mutable pointer layer), or stay mutable-URI + ETag revalidation, or mix by resource class? 4. **Invalidation transport.** Should the invalidation signal ride **paths-as-event-bus** (PUT/observe on an `invalidate` path), the dock "hey you, it's me" message, or a dedicated channel? Who is allowed to emit one (token scope)? 5. **Boundary with `4c6470f9`.** Confirm: model here, mechanism in the orchestrator (or its child)? Or should this bead own a running cache-manager too? 6. **First artifact.** Once framed, which is worth building first — an `invalidate` signal format + emitter, a `cache-manifest.mjs` (enumerate what a node/browser holds for a path), or a stale-while-revalidate SW reference?
## Next actions - Get Stephen's answer to Q1 (it gates everything else). - Then draft the chosen first artifact (Q6) as a `skills/` or `artifacts/` deliverable.
## References (bead cross-links) - Bead: 874fce5b · [canonical](https://redfish.acequia.io/guerin/.agents/874fce5b-9c8b-4b23-b2ed-429148c6c4b7/)