6. Goal & constraints
Source path: knowledge-base/knowledge/architecture/persistence/sync-foundations.md
# 6. Goal & constraints
- **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 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.
Open on GitHubRaw Markdown source