MeridiansMeridians

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

RoleWhoAuthority
MasterThe Director's browser on localhost/127.0.0.1Owns 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 a displaced frame).
  • 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, optional ackPushId), entries, removed, reject, disconnect, asset-data/asset-data-batch. The broker itself fans asset-notice (record → clients; id-only, member-gated — see §12).
  • Broker → host: roster, asset-request/asset-request-batch, plus the relayed client kinds. displaced to a superseded host.