# Sync

Postgres → Electric SQL shapes → TanStack DB collections → Svelte components. Every house-scoped table carries a flat `house_id` so the Electric `where` stays a literal equality (Electric's optimised fast path; subqueries and `ANY()` over arrays degrade). Per-entity tables, schemas in `@arbe/core/schemas/rows.ts`, single-table shapes — no joins on the wire; relations = multiple shapes the client glues.

```
login         → mount discovery shape  (members WHERE agent_id = $me)
enter house   → mount content shapes   (houses, environments, configs, threads
                                         WHERE house_id = $hid AND deleted_at IS NULL;
                                         agents arrive via the members-join shape — no house_id column)
switch house  → unmount old content, mount new; discovery untouched
logout        → unmount everything
```

A central **shape registry** owns mounting/unmounting, driven by auth + route state — components read live collections and stay ignorant. Shapes are immutable per subscription: a membership change produces a different `where` on the next request and a full re-sync. Optimistic writes apply locally, POST to the matching per-entity route (`createHouse`, `updateThread`, …), and reconcile on the `txid` Electric replays back.

`agents` is the one v1 table without a `house_id` scope (one identity, N houses). `members` is the house-scoped access edge: `(house_id, agent_id, role)`, plus denormalised `display_name` + `kind` for render paths — Discord's `User` vs `GuildMember` split. A house roster is `members` joined to `agents`. The full `agents` row carries settings and is mounted only when a user edits their own profile or a bot they admin; rename = server-side fan-out across that agent's `members` rows.

`member` is the term Discord, Slack, GitHub, Linear, Notion all use. House membership grants access to every thread under the house. Ownership lives on `members.role = 'owner'`; there's no `author_id` on houses. RLS gates every table by `is_house_member` / `is_house_owner`; the shape `where` mirrors the same predicate so unauthorised rows never leave the server. `members` RLS in v1 is peer-visible within shared houses, so a member can see the house roster.

Code: `packages/core/schemas/rows.ts` (`HouseRowSchema`, `RoomRowSchema`, `MemberRowSchema`, `AgentRowSchema`, plus `AgentKind` / `MemberRole` / `TriggerMode` enums), `apps/www/src/routes/api/shapes/`, `apps/www/src/lib/collections/`.<br>
See [system/storage](./storage.md), [system/permissions](./permissions.md), [website/www](../website/www.md).

_Open: single mutation endpoint vs per-entity POSTs; conflict reconciliation strategy; admin listing of other house members (relax RLS conditionally vs separate endpoint outside sync); whether Electric carries a live connection across a JWT refresh or we tear down + remount._
