6. Goal & constraints
From the Meridians Wiki · Public · Maintained · joint
- One world, many people. A Director runs Meridians locally; teammates/admins reach the same instance over an ngrok tunnel and work together in real time.
- Sovereign / local-first. No cloud backend. The Director's machine is the system of record (Part A): the record on disk is canon; the only server is the Director's own Next process, which the tunnel proxies.
- Single source of truth. The master owns the canonical record. Every change is reconciled through it, so there is one canonical history.
- Fast reads, deliberate writes. Viewing is local and instant on every node; only writes pay a round-trip.
- Admins ≈ Directors (for now). Clients are treated as admin-privileged members with full capability. Permissions can be tightened later at a small number of gates.
7. Roles
| Role | Who | Authority |
|---|---|---|
| Master | The Director's browser on localhost/127.0.0.1 | Owns the canonical record (fs, Part A); authoritative reducer; hosts the broker |
| Client (prose: "clone") | Any other origin (the tunnel) | Local IndexedDB replica + reducer; optimistic edits pushed to the master |
Role is decided by origin, once per session — getSyncRole() (src/lib/server/sync/role.ts).
Localhost ⇒ master, everything else ⇒ client. The browser only reaches its own daemon when
daemonEligible() (master + not latched-off + in a window); the broker enforces a single host as a
backstop (a second localhost tab is displaced, see §16).
8. Topology
ngrok tunnel (one public URL → the Director's Next process)
│
client browsers ──────────┼──────────── master browser (localhost)
(IndexedDB replica) │ (owns the canonical record — Part A)
│ SSE down / HTTP up │ SSE host stream / HTTP publish
└──────────► in-process BROKER ◄──────────┘
(globalThis, src/lib/server/sync/broker.ts)
One Next process. The broker is an in-process hub on globalThis (survives HMR). It holds NO
authority — it relays messages, retains the lightweight entries/open-games registries, and keeps a
byte-bounded asset working set. Full Domain and Constellation documents are live frames, not server-cache
residents; a reconnect requests the fresh canonical document from the master. The tunnel proxies the same
process, so clients reach the exact broker the master hosts.
9. The broker — src/lib/server/sync/broker.ts
host: Sink | null— the single master SSE sink (single-host; last localhost wins, prior host gets adisplacedframe).clients: Set<Sink>+cloneIds+presence— client connections, their ids, and their live "what am I viewing" rows (the Director roster).entries+openGames— lightweight reconnect state. Full documents are requested from the master and fanned out without being retained by the broker.assets+assetWaiters+batchWaiters— content-asset cache and the pending request/response correlation for the asset relay.- Functions:
subscribeHost,subscribeClient,relayToHost,publishDomain,publishEntries,publishRemoved,publishReject,requestAsset,requestAssets,provideAsset/provideAssets,updatePresence,disconnectAllClients.
10. Wire protocol — src/lib/server/sync/protocol.ts
Two HTTP routes carry everything: POST /api/sync (messages), GET /api/sync/stream (SSE; ?role=host
for the master, else a client, with cloneId). Asset bytes use GET /api/sync/asset?id= (single) and
POST /api/sync/assets (bulk).
- Client → master (relayed to host):
action(a domain edit + target domain/branch +pushId),create(new full domain),delete,request(lazy pull a domain),pull(ask for the entry list),asset-upload(a client-created asset),presence. - Master → broker → clients:
domain(canonical doc, optionalackPushId),entries,removed,reject,disconnect,asset-data/asset-data-batch. The broker itself fansasset-notice(record → clients; id-only, member-gated — see §12). - Broker → host:
roster,asset-request/asset-request-batch, plus the relayed client kinds.displacedto a superseded host.