eleven / messenger Public

docs: planet-scale.md — the mesh moved from roadmap to running (#678, #681) (#683)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 194ff0e07944183175acca55df4a25888bc33bd1 parent eba49e3
1 file changed, +34 −6
  • docs/planet-scale.md +34 −6
diff --git a/docs/planet-scale.md b/docs/planet-scale.md
index d6bc78c..b18110c 100644
--- a/docs/planet-scale.md
+++ b/docs/planet-scale.md
@@ -230,6 +230,39 @@ immortal. The stats handler now answers those **definitionally from the
registry row** (0 members, never set up, no content); a missing DB on a
*non*-hibernating row stays a 404, meaning a failed create, skipped as before.
+### The mesh: multiple boxes, one owner per instance
+
+The multi-box mechanism from Part 4 v2 shipped as PR #678, gated on
+`LCHAT_MESH_SELF` (a peer-reachable base URL); absent means zero mesh
+codepaths, and the current single-box deployment runs exactly as before.
+
+- **Membership is S3 heartbeats**, not gossip: each box PUTs one object per
+ 15 seconds under the hibernation prefix's `mesh/members/`, and the object
+ *key* is the record (base64url of the peer URL) so a single LIST yields the
+ whole membership. Liveness reads S3's own `LastModified`, so a box with a
+ wrong clock can't poison the staleness judgment. Stale after 45s.
+- **Rendezvous (HRW) hashing is the placement hint** — `owner(host)` is a
+ pure function of the live member set; adding a box moves only the hosts
+ that land on it. **The lease stays the truth**: a box that wrongly believes
+ it owns a host loses the CAS race loudly and proxies instead, so transient
+ membership disagreement structurally cannot split-brain.
+- **Wrong-box requests reverse-proxy to the owner**, TLS verified against the
+ instance hostname with the connection pinned to the peer's address (no
+ `InsecureSkipVerify` anywhere); a one-hop marker turns disagreement loops
+ into a wrong-box wake the lease resolves.
+- **Drain deletes the heartbeat first**, then checkpoints and releases as
+ usual — so deploys, scale-down, and box death are the same code path. The
+ kill-a-box property is a live test, both ways: a drained owner's instances
+ are serving on the survivor ~2 seconds later; a SIGKILLed owner's within
+ ~30 (the lease TTL), data intact in both.
+
+Hardening that same week (PR #681, both lessons from the first real
+production flip): flipping a host refuses while the box-wide backup
+replicator still tracks it — two litestream processes on one DB share a
+derived meta directory and corrupt each other — and the lease release runs
+under its own budget so a wedged final checkpoint can't strand the lease
+until TTL.
+
### The seams cut for what's next
Small PRs that exist so later phases are config, not surgery:
@@ -281,7 +314,7 @@ means the decision stands with nothing built against it yet.
| 4 | metadata at rest | sealed bucket: HMAC(pepper) keys + pepper-encrypted metadata | **Not built** — object keys are plain uids; bucket stays private (Block Public Access) |
| 5 | public bucket | design as if public; the flip is its own reviewed decision (takedown process prerequisite) | Held — posture unchanged |
| 6 | instance runtime | hibernating processes over S3: local SQLite + Litestream, lease = single-writer lock (over Lambda / S3-as-only-DB / cells / renting DO-Fly) | **Shipped** #630/#641 — with the correction: the lease is Go-library-only; the CLI silently absorbs a second writer, so the activator embeds litestream |
-| 7 | placement | rendezvous hash as hint, S3 lease as truth (over a routing table) | Lease half **shipped**; gossip/HRW is roadmap — one box, hash picks self |
+| 7 | placement | rendezvous hash as hint, S3 lease as truth (over a routing table) | **Shipped** (#678) — S3-heartbeat membership + HRW + proxy-to-owner; kill-a-box passes live both ways |
| 8 | instance data | one SQLite per space, forever (shared DB tier is the anti-goal) | Held, load-bearing |
| 9 | attachments | blob interface → R2 from day one | Seam **shipped** #636; disk impl only, R2 roadmap |
| 10 | provisioning | create = register only; materialize at first wake; controld dissolves | Split **shipped** #625, hibernate-by-default creates #663; full deferral + controld dissolution roadmap |
@@ -339,11 +372,6 @@ PR that touches this territory gets checked against these:
Everything below is settled design from #550 with no code behind it (or only
a seam). Build triggers are events, not dates.
-- **The activator mesh** — the "second box" work, in progress: membership
- (gossip or S3 heartbeats), rendezvous hashing as the placement hint, peer
- proxying to the hash-owner. The lease already guarantees split-brain can't
- happen; the mesh only decides who *tries* first. Today's single router box
- is the degenerate case — membership of one, hash picks self.
- **Multi-region / `home_region`** — one write region per space, wakes
deterministic, relocation *while hibernated* (copy the prefix, flip the
field, next wake claims the lease elsewhere). The field exists; nothing