latticesql 2.3.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (7) hide show
  1. package/README.md +124 -146
  2. package/dist/cli.js +56850 -20094
  3. package/dist/index.cjs +47648 -8527
  4. package/dist/index.d.cts +802 -633
  5. package/dist/index.d.ts +802 -633
  6. package/dist/index.js +47080 -8003
  7. package/package.json +2 -1
package/README.md CHANGED
@@ -2161,11 +2161,22 @@ Chat threads, files, and secrets are all stored as native Lattice entities.
2161
2161
 
2162
2162
  The convergence means you don't need to duplicate entity-context definitions in YAML for the GUI to find rendered files.
2163
2163
 
2164
- **Database wizard form (v1.13.2+).** The Postgres connection form (used by Migrate to cloud + the Join-a-team invite flow) disables browser autocapitalize, autocorrect, and spellcheck on every text input, and trims whitespace on every read. This avoids silent failure modes where macOS Safari / iOS turned a Supabase tenant user `postgres.<ref>` into `Postgres.<ref>` on submit, and where pasted credentials carrying a trailing newline produced opaque "zero-length delimiter identifier" or SCRAM-mismatch errors. `probeCloud` also folds SQLSTATE + `routine` into `result.error` so the GUI's "Unreachable: …" surface is actionable.
2165
-
2166
- **Migrate vs. join (1.16.4).** The standalone "Connect to existing cloud" wizard (which switched a project's `db:` line to a raw cloud on its own) was removed — the two cloud operations are now **Migrate to cloud** (push your local workspace's data into a fresh cloud Postgres; you become the owner) and **Join a team (invite)** (redeem an invite token to join a workspace someone shared with you). The `connect-existing` endpoint still backs the invite‑redeem path for an active cloud that needs an invite.
2167
-
2168
- **Cloud workspaces initialize automatically (v1.16.3+).** A cloud database _is_ a cloud workspace with members — there is no separate "upgrade to team" step. The moment a database is migrated or connected to Postgres, its member/share machinery is created automatically (the workspace name is used as the identity; an existing un-initialized cloud initializes on open, with the opener as owner). The underlying mechanism is the `registerDirectViaPostgres()` helper, which drives the identity/member INSERT sequence directly against the cloud Postgres (the older HTTP `/api/auth/register` path is still used when the cloud URL is `http(s)://`). The standalone "Upgrade to team cloud" action and its `/api/dbconfig/upgrade-to-team` route were removed in 1.16.3.
2164
+ **Database wizard form (v1.13.2+).** The Postgres connection form (used by Migrate to cloud + the Join-a-cloud flow) disables browser autocapitalize, autocorrect, and spellcheck on every text input, and trims whitespace on every read. This avoids silent failure modes where macOS Safari / iOS turned a Supabase tenant user `postgres.<ref>` into `Postgres.<ref>` on submit, and where pasted credentials carrying a trailing newline produced opaque "zero-length delimiter identifier" or SCRAM-mismatch errors. `probeCloud` also folds SQLSTATE + `routine` into `result.error` so the GUI's "Unreachable: …" surface is actionable.
2165
+
2166
+ **Migrate vs. join.** The two ways onto a cloud are **Migrate to cloud** (push your
2167
+ local workspace's data into a fresh cloud Postgres; Lattice installs RLS and you
2168
+ become the owner of every migrated row) and **Join a cloud** (connect directly with
2169
+ the scoped credentials the owner handed you — host / port / database / username /
2170
+ password — which _are_ the invite; there is no token to redeem). The
2171
+ `connect-existing` endpoint backs the join: it probes the target as your role,
2172
+ confirms it is a Lattice cloud (RLS installed), and opens it in introspect-only mode.
2173
+
2174
+ **A cloud is just a secured Postgres database.** Migrating to cloud installs the RLS
2175
+ machinery (`installCloudRls` + `enableRlsForTable` per table) and stamps you as owner
2176
+ of the migrated rows — there is no separate "upgrade to team" step and no shared team
2177
+ identity to bootstrap. Membership is purely the set of Postgres roles the owner has
2178
+ provisioned; a member joins by connecting as their own scoped role, and RLS confines
2179
+ them to their own + shared rows.
2169
2180
 
2170
2181
  **Dashboard renders every entity (v1.13.3+).** Previously the dashboard cards filtered through a hardcoded entity list (`meetings`, `people`, `messages`, `projects`, `repositories`, `files`). Installs whose YAML declared different names saw a blank dashboard. Now every first-class entity gets a card; the hardcoded list survives as an ordering preference only.
2171
2182
 
@@ -2179,7 +2190,7 @@ The convergence means you don't need to duplicate entity-context definitions in
2179
2190
  - **Table view** (`#/objects/<entity>`, Advanced mode) — intrinsic columns, `belongsTo` chips, and a column per junction this entity participates in.
2180
2191
  - **Detail view** (`#/objects/<entity>/<id>`, Advanced mode) — read mode by default; `Edit` flips cells into inputs (`Save` PATCHes, `Cancel` reverts).
2181
2192
  - **Settings** (v2.0+) — opened from the header gear (Database / Lattice / User tabs + the Advanced-mode toggle); the legacy `#/settings/*` hashes still resolve and open the drawer.
2182
- - **Data Model** (inside **Workspace Settings**, v1.14+) — entity-level graph including the native `files`/`secrets` objects, with a per-entity editor. On a cloud workspace each table you own carries a **Share with workspace / Make private** toggle, and graph nodes are colored by share status (yellow = shared, red = private, green = selected) with a legend. (Pre-1.14 this was a separate `#/settings/data-model` nav item; that hash still resolves for back-compat.)
2193
+ - **Data Model** (inside **Workspace Settings**, v1.14+) — entity-level graph including the native `files`/`secrets` objects, with a per-entity editor. (Pre-1.14 this was a separate `#/settings/data-model` nav item; that hash still resolves for back-compat.) On a cloud, sharing is per-**row** under Postgres RLS, not per-table a row's owner sets its visibility (`private` | `everyone`) via the owner-only `lattice_set_row_visibility` SQL function; see [docs/cloud.md](docs/cloud.md).
2183
2194
 
2184
2195
  **Internal tables added on first open**
2185
2196
 
@@ -2195,22 +2206,22 @@ These tables are prefixed with `_lattice_gui_` and are hidden from `/api/entitie
2195
2206
 
2196
2207
  **HTTP surface** (all routes scoped to `http://127.0.0.1:<port>/api`):
2197
2208
 
2198
- | Route | Method | Lattice call |
2199
- | ------------------------------ | ------ | ------------------------------------------------------------------- |
2200
- | `/project` | GET | (config + manifest summary) |
2201
- | `/entities` | GET | tables + `db.count` per table |
2202
- | `/graph` | GET | (schema graph for Data Model) |
2203
- | `/tables/:table/rows` | GET | `db.query(table, …)` |
2204
- | `/tables/:table/rows` | POST | `db.insert(table, body)` |
2205
- | `/tables/:table/rows/:id` | GET | `db.get(table, id)` |
2206
- | `/tables/:table/rows/:id` | PATCH | `db.update(table, id, body)` |
2207
- | `/tables/:table/rows/:id` | DELETE | `db.delete(table, id)` |
2208
- | `/tables/:junction/link` | POST | `db.link(junction, body)` |
2209
- | `/tables/:junction/unlink` | POST | `db.unlink(junction, body)` |
2210
- | `/schema/entities` | POST | create a new entity/table |
2211
- | `/schema/entities/:name/share` | POST | share/unshare a table you own with the cloud workspace (owner-only) |
2212
-
2213
- On a cloud workspace, `/entities` and `/graph` (and the queryable `/tables/*` allowlist) are filtered to the tables you own plus tables shared to the workspace so the API surface matches exactly what the GUI shows; a table you can't see is not reachable. `/entities` rows carry `shared` / `ownedByMe` flags in that mode.
2209
+ | Route | Method | Lattice call |
2210
+ | -------------------------- | ------ | ----------------------------------------------------------- |
2211
+ | `/project` | GET | (config + manifest summary) |
2212
+ | `/entities` | GET | tables + `db.count` per table |
2213
+ | `/graph` | GET | (schema graph for Data Model) |
2214
+ | `/tables/:table/rows` | GET | `db.query(table, …)` |
2215
+ | `/tables/:table/rows` | POST | `db.insert(table, body)` |
2216
+ | `/tables/:table/rows/:id` | GET | `db.get(table, id)` |
2217
+ | `/tables/:table/rows/:id` | PATCH | `db.update(table, id, body)` |
2218
+ | `/tables/:table/rows/:id` | DELETE | `db.delete(table, id)` |
2219
+ | `/tables/:junction/link` | POST | `db.link(junction, body)` |
2220
+ | `/tables/:junction/unlink` | POST | `db.unlink(junction, body)` |
2221
+ | `/schema/entities` | POST | create a new entity/table |
2222
+ | `/cloud/share` | POST | row owner sets a row's visibility (`private` \| `everyone`) |
2223
+
2224
+ On a cloud, you connect as your own scoped Postgres role and **Row-Level Security filters every read and write at the database level** a row another member hasn't shared with you simply isn't returned by `/tables/:table/rows`, because Postgres itself excludes it. The GUI shows exactly what RLS lets your role see; there is no application-layer allowlist to keep in sync. See [docs/cloud.md](docs/cloud.md).
2214
2225
 
2215
2226
  The server only binds to `127.0.0.1` and has no authentication. See [SECURITY.md](./SECURITY.md) for the threat model — do not expose this port to a non-loopback interface.
2216
2227
 
@@ -2249,36 +2260,34 @@ await adoptNativeEntities(db); // merge + label existing files/secrets as native
2249
2260
  const bindings = await listNativeBindings(db); // [{ entity, tableName, origin }]
2250
2261
  ```
2251
2262
 
2252
- **Machine-local user config at `~/.lattice/` (v1.12+).** A small set of files outside any Lattice DB so a user's identity, encrypted master key, saved cloud-DB credentials, and per-team bearer tokens survive switching projects:
2263
+ **Machine-local user config at `~/.lattice/` (v1.12+).** A small set of files outside any Lattice DB so a user's identity, encrypted master key, and saved cloud-DB credentials survive switching projects. A member's cloud credential is simply their own scoped `postgres://` URL — there are no bearer tokens; the connection string the owner issued is the whole credential:
2253
2264
 
2254
2265
  | File | Purpose |
2255
2266
  | ------------------------------- | -------------------------------------------------------------------------------------------------------- |
2256
2267
  | `~/.lattice/master.key` | 32-byte AES-256 master key, auto-generated, `chmod 0600` on POSIX |
2257
2268
  | `~/.lattice/identity.json` | `{display_name, email}` — mirrored into the active Lattice's `__lattice_user_identity` row on every open |
2258
- | `~/.lattice/keys/<label>.token` | Per-joined-team bearer tokens (`chmod 0600`) |
2259
- | `~/.lattice/db-credentials.enc` | AES-GCM-encrypted Postgres URLs keyed by label |
2269
+ | `~/.lattice/db-credentials.enc` | AES-GCM-encrypted Postgres URLs keyed by label (your scoped cloud connection strings) |
2260
2270
 
2261
2271
  The GUI's User Settings view edits `identity.json` directly; the Database Settings page writes saved Postgres URLs into `db-credentials.enc` and rewrites `lattice.config.yml`'s `db:` line to `${LATTICE_DB:<label>}`. The config parser resolves that reference at open time so connection passwords never sit in YAML on disk.
2262
2272
 
2263
2273
  `~/.lattice/preferences.json` (v1.13.8+) holds machine-local UI preferences keyed by name. Currently a single flag `show_system_tables: boolean` (default `false`) — see _Sidebar system-tables toggle (v1.13.8+)_ below.
2264
2274
 
2265
- **Information architecture, v1.13.8+.** The GUI treats every database as either Local (single-user SQLite) or Cloud (Postgres, one or more invited team members), with the database itself as the first-class concept. "Team" describes the set of members on a cloud database — there is no separate "create a team" step, and the verbiage for member management ("Invite Team", "Join Team", "Team Members") stays where it belongs. Concretely:
2275
+ **Information architecture, v1.13.8+.** The GUI treats every database as either Local (single-user SQLite) or Cloud (a shared Postgres with one or more members), with the database itself as the first-class concept. A cloud _is_ the set of members who can connect to it — there is no separate "create a team" step, and member management is the cloud's Invite / Members surface (an owner provisions a scoped role; a member joins by connecting as it). Concretely:
2266
2276
 
2267
- - **Editable database name** at the top of Database Settings. For cloud DBs the rename writes `__lattice_team_identity.team_name` (and broadcasts to every member via the realtime channel below); for local DBs it writes a `name:` key into the YAML config, parsed by `parseConfigFile()` and surfaced as `ParsedConfig.name`. New endpoint `POST /api/dbconfig/rename`.
2268
- - **Three-step Create Database wizard.** Opened from the header dropdown's `+ New database` button and from Lattice Settings → Add new database. Step 1: name + Local|Cloud + cloud credentials. Step 2: starter entities, each with a pre-checked "Share with cloud" checkbox when cloud. Step 3: review + create.
2277
+ - **Editable database name** at the top of Database Settings. A cloud carries no shared name; the name is the operator's own workspace label so rename always writes a `name:` key into the YAML config (parsed by `parseConfigFile()`, surfaced as `ParsedConfig.name`) and mirrors it into the workspace registry, for both local and cloud configs. Endpoint `POST /api/dbconfig/rename`.
2278
+ - **Three-step Create Database wizard.** Opened from the header dropdown's `+ New database` button and from Lattice Settings → Add new database. Step 1: name + Local|Cloud + cloud credentials. Step 2: starter entities. Step 3: review + create.
2269
2279
  - **Header dropdown** shows the friendly database name + a Local|Cloud kind chip + a connectivity dot per row (green = cloud live, yellow = local SQLite, red = cloud disconnected). The `+ New database` button at the bottom opens the wizard.
2270
2280
  - **Settings sidebar** is reorganized into Lattice Settings (catalog of all databases this lattice can reach + Add-new entry), Database Settings (renamed from Project Config — editable name header on top, the existing Database panel and cloud-databases list below), Data Model, and User Settings. The legacy `/settings/project-config` route still resolves for back-compat.
2271
- - **Migrate-to-Cloud per-table share checkboxes.** The migrate modal lists every user-defined table with "Share with cloud" pre-checked; uncheck individual tables to keep them cloud-stored but unshared. After the migrate, only checked tables call `shareObject`.
2272
- - **New-entity flow** on a cloud-connected database pre-checks a "Share with cloud" box; the share runs best-effort after the entity is created.
2281
+ - **Sharing is per-row, after migration.** On a cloud every row is private to its owner by default; the owner opts individual rows into `everyone` via `/api/cloud/share` (the `lattice_set_row_visibility` SQL function). There is no per-table "share" toggle RLS works at the row level.
2273
2282
 
2274
- **Realtime cloud subscriptions (v1.13.8+).** Cloud Postgres-backed lattices stream changes to every connected GUI in realtime. A Postgres trigger on `__lattice_change_log` emits `pg_notify('lattice_changes', …)` after every insert; the GUI server holds a dedicated `pg.Client` with `LISTEN lattice_changes` and fans payloads out via a new Server-Sent Events endpoint:
2283
+ **Realtime cloud subscriptions (v1.13.8+).** Cloud Postgres-backed lattices stream changes to every connected GUI in realtime. The per-table RLS trigger writes each insert/update/delete to the append-only `__lattice_changes` feed, whose `AFTER INSERT` trigger fires `pg_notify('lattice_changes', …)` with only metadata (table, pk, op) — never row content. The GUI server holds a dedicated `pg.Client` with `LISTEN lattice_changes` and fans payloads out via a Server-Sent Events endpoint; the browser refetches the affected row _through RLS_, so another member's content is never broadcast:
2275
2284
 
2276
2285
  | Route | Method | Description |
2277
2286
  | ---------------------- | ------ | -------------------------------------------------------------------------------- |
2278
2287
  | `/api/realtime/stream` | GET | SSE stream; `event: state` on connection transitions, `event: change` per NOTIFY |
2279
2288
  | `/api/realtime/status` | GET | JSON snapshot of `{ mode: 'local'\|'cloud', state, connected }` |
2280
2289
 
2281
- The browser's `EventSource` invalidates the entity cache on every `change` event; connection state drives a colored dot in the topbar (green/yellow/red). SQLite databases are unchanged — LISTEN/NOTIFY is Postgres-only and the broker is skipped on those. The trigger installer (`installCloudInternalTriggers`) is exported from `latticesql/teams/internal-tables` for callers that bootstrap cloud schemas outside the GUI.
2290
+ The browser's `EventSource` invalidates the entity cache on every `change` event; connection state drives a colored dot in the topbar (green/yellow/red). SQLite databases are unchanged — LISTEN/NOTIFY is Postgres-only and the broker is skipped on those.
2282
2291
 
2283
2292
  **Sidebar system-tables toggle (v1.13.8+).** Internal `__lattice_*` and `_lattice_gui_*` tables are hidden from the sidebar by default. Enable the "Show system tables in sidebar" checkbox in User Settings → Preferences to surface them under a "System" section. Persisted to `~/.lattice/preferences.json`; exposed via `GET`/`POST /api/userconfig/preferences`.
2284
2293
 
@@ -2305,8 +2314,9 @@ the library API is unchanged and fully backwards-compatible.
2305
2314
  revertible via the version history.
2306
2315
  - **Inference Aggressiveness** slider tunes how much the assistant extrapolates
2307
2316
  (temperature + link liberality + auto-junction/auto-create gating).
2308
- - Runs on local SQLite and a direct `postgres://` connection; not yet in hosted
2309
- multiplayer team-cloud mode.
2317
+ - Runs on local SQLite and on any `postgres://` connection, including a Lattice
2318
+ cloud — where it connects as your own scoped role, so its reads and writes are
2319
+ RLS-confined to the rows you may see, exactly like the rest of the GUI.
2310
2320
 
2311
2321
  The same intelligence is exposed as a **first-class library API** (inert without an
2312
2322
  LLM client): `organizeSource`, `describeImage`, `crawlUrl`, `enrichKnowledge`, and
@@ -2316,137 +2326,105 @@ See [docs/assistant.md](docs/assistant.md) for the full guide.
2316
2326
 
2317
2327
  ---
2318
2328
 
2319
- ## CLI`lattice teams` (v1.12+)
2320
-
2321
- Multi-user cloud-shared Lattice databases on your own Postgres. Bring your own Postgres connection; lattice handles identity (bearer tokens, email-bound invitations), the team membership table, and the sync engine (shared objects + row links + change feed + outbox + replay-guard puller).
2322
-
2323
- **Bootstrap** (on a fresh cloud — no users yet):
2324
-
2325
- ```bash
2326
- lattice teams register \
2327
- --cloud http://localhost:4317 \
2328
- --email alice@example.com \
2329
- --name "Alice" \
2330
- --team-name "Atlas"
2331
- ```
2332
-
2333
- Atomic: creates the user, the singleton team, the creator membership, and the bearer token in one HTTP call. Prints the token once — save it locally.
2334
-
2335
- **Invite a teammate by email**:
2336
-
2337
- ```bash
2338
- lattice teams invite --team Atlas --invitee-email bob@example.com
2339
- # → prints `latinv_…` token to share OOB with bob@example.com
2340
- ```
2341
-
2342
- **Join an existing team**:
2343
-
2344
- ```bash
2345
- lattice teams join \
2346
- --cloud http://localhost:4317 \
2347
- --token latinv_… \
2348
- --email bob@example.com \
2349
- --name "Bob"
2350
- ```
2351
-
2352
- The cloud rejects redemption if the caller's claimed email doesn't match the invitation's `invitee_email` (case-insensitive). Sharing an invite token in a public channel is therefore safe — only the addressee can redeem it.
2353
-
2354
- **Other subcommands** (`lattice teams help` for the full list): `list`, `members`, `leave`, `destroy`, `share`, `unshare`, `shared`, `sync`, `link`, `unlink`, `pull`, `push`, `status`, `dlq`.
2329
+ ## Cloudshared Postgres with Row-Level Security (v3.0+)
2355
2330
 
2356
- **Dead-letter queue (v1.15+).** A pulled change envelope that fails to apply (e.g. it arrived before the row/table it depends on), and any non-owner-overwrite divergence notice, lands in `__lattice_team_dlq`. Inspect and recover it instead of losing it behind the pull cursor:
2331
+ A **Lattice cloud** is a shared Postgres database secured by real Postgres
2332
+ **Row-Level Security**. Several people connect to the same database, each as their
2333
+ own scoped, non-superuser role, and each sees only their own rows plus the rows
2334
+ others have shared. **There is no server** — no HTTP API in front of Postgres, no
2335
+ bearer tokens, no replica, no sync client. The database _is_ the security boundary:
2336
+ a member with full SQL access to their own connection physically cannot read or
2337
+ write another member's rows.
2357
2338
 
2358
- ```bash
2359
- lattice teams dlq list --team <name> # show entries (op, target, error)
2360
- lattice teams dlq retry --team <name> [--id <id>] # replay; a late dependency now applies cleanly
2361
- lattice teams dlq purge --team <name> [--id <id>] # discard without applying
2362
- ```
2339
+ A cloud **is** the set of people who can connect to it — there is no separate "team"
2340
+ object and no "enable sharing" step. The DBA only sets up the Postgres database and
2341
+ creates usernames/passwords; Lattice installs the rest with plain SQL (`CREATE ROLE`,
2342
+ `CREATE POLICY`, `FORCE ROW LEVEL SECURITY`, `SECURITY DEFINER` functions). Identity
2343
+ is the Postgres role: policies key on `session_user` / `current_user`, which stays
2344
+ reliable behind a transaction-mode connection pooler.
2363
2345
 
2364
- **Per-table ownership + opt-in sharing (v1.14+).** Team members share one physical Postgres, so visibility is enforced at the app layer via a `__lattice_object_owners` table: each table records its creator, and a user sees only the tables they own plus tables explicitly shared to the team. The native `files`/`secrets` objects are owned by the database creator and private by default. Sharing is an explicit, owner-only action (not a side effect of creating a table). The filter gates API access, not just the display.
2346
+ SQLite stays single-user and local RLS is Postgres-only. The bridge from a private
2347
+ local Lattice to a shared one is **migrate**.
2365
2348
 
2366
- **Row-level permissions (v2.2+).** Within a shared table, each row carries an owner (its creator) and a visibility — `private`, `everyone`, or `custom` (an explicit grant list) — enforced for the REST API, the AI assistant, and the cloud sync, so a member never receives the bytes of a row they can't read. Existing shared tables default to `everyone` on upgrade. The hosted Teams server filters the change-log pull per recipient; direct `postgres://` connections (which can't enforce this) are deprecated in favour of a hosted server. See `docs/teams.md`.
2349
+ **Three flows:**
2367
2350
 
2368
- **Same flows from the GUI (v1.14+).** The local `lattice gui` drives the entire cloud-workspace lifecycle from **Workspace Settings**: rename (owner-only), invite by email (owner-only), the inline Members list with pending invitees (the owner is always shown as `creator`; your own row offers Leave/Destroy; non-owners can't kick), share/unshare from the Data Model, and sync status. Member admin is resolved from `GET /api/dbconfig` against the active cloud DB, so it works even when the cloud workspace itself is the active database. Identity (display name + email) comes from `~/.lattice/identity.json` and is locked in the Join modal. Leaving a workspace removes the local config + credential and switches you to another database.
2351
+ - **Migrate** point a local Lattice at a fresh Postgres; Lattice copies your data
2352
+ in, installs RLS, and makes you the owner of every migrated row.
2353
+ - **Join** — connect directly with the scoped credentials the owner gave you (host /
2354
+ port / database / username / password). **Those credentials _are_ the invite** —
2355
+ there is no token to redeem and no server to sign into.
2356
+ - **Invite** — an owner (whose role holds `CREATEROLE`) provisions a scoped,
2357
+ `NOSUPERUSER` member role and hands the new member that connection blob.
2369
2358
 
2370
- **Joining via the GUI is one click (v1.13.7+).** When you click "Join via invite" and the redeem succeeds, the team's cloud URL is automatically saved as a switchable database credential and a sibling YAML config is written to your project directory. The new entry shows up in the database dropdown as `<team-name>.config`. Clicking it opens the SPA with the team's shared tables already populated — no YAML editing, no `db.define()` calls.
2359
+ **Sharing is private-by-default.** Every row is owned by whoever wrote it and starts
2360
+ `private`; the owner opts a row into `everyone` (or back to `private`) through the
2361
+ owner-only SQL function:
2371
2362
 
2372
- **Cloud server mode**: `lattice gui --team-cloud` boots the same binary as a cloud server. It exposes the bearer-token-gated `/api/team*` endpoints + the `/objects`/`/changes`/`/rows`/`/links` sync routes, and disables the local dev-tool surface (table viewer, CRUD endpoints, register-and-create modal).
2373
-
2374
- The full architecture, schema, and HTTP surface live in [docs/teams.md](./docs/teams.md).
2375
-
2376
- ---
2377
-
2378
- ## Cloud migration + connection (v1.13+)
2379
-
2380
- Lattice Teams + the GUI's Database panel now flow through a state machine:
2381
-
2382
- ```
2383
- LOCAL → CLOUD WORKSPACE (owner | member)
2384
- (migrate / connect)
2363
+ ```sql
2364
+ SELECT lattice_set_row_visibility('items', 'item-42', 'everyone');
2385
2365
  ```
2386
2366
 
2387
- Migrating or connecting to Postgres produces a cloud workspace directly its member/share machinery is initialized automatically, with no separate "upgrade" step (the intermediate `cloud-connected` state was removed in 1.16.3). The transition is one-way: once on cloud, the panel does not surface a revert-to-local button. Disconnecting from the cloud temporarily is a follow-up; the in-place reconnection happens automatically when the GUI reopens.
2388
-
2389
- Public API surface (the GUI's `/api/dbconfig/*` routes are thin wrappers):
2367
+ **Library API**all the cloud helpers import from `latticesql`:
2390
2368
 
2391
2369
  ```ts
2392
2370
  import {
2393
2371
  Lattice,
2372
+ // migrate a local Lattice into a fresh cloud Postgres
2373
+ openTargetLatticeForMigration,
2394
2374
  migrateLatticeData,
2375
+ installCloudRls,
2376
+ backfillOwnership,
2377
+ enableRlsForTable,
2395
2378
  archiveLocalSqlite,
2396
- openTargetLatticeForMigration,
2379
+ // owner provisions / revokes scoped member roles
2380
+ memberRoleName,
2381
+ generateMemberPassword,
2382
+ provisionMemberRole,
2383
+ revokeMemberRole,
2384
+ // sharing + probe
2385
+ setRowVisibility,
2397
2386
  probeCloud,
2398
- TeamsClient,
2399
2387
  } from 'latticesql';
2400
2388
 
2401
- // 1. Migrate a local SQLite project to a fresh cloud Postgres
2402
- const source = new Lattice({ config: './lattice.config.yml' }, { encryptionKey });
2403
- await source.init();
2404
- const target = await openTargetLatticeForMigration('./lattice.config.yml', cloudUrl, encryptionKey);
2405
- const result = await migrateLatticeData(source, target);
2406
- // → { tablesCopied: ['files','items','secrets',...], rowsCopied: 42 }
2407
- target.close();
2408
- archiveLocalSqlite('./data/project.db'); // renames to .db.local-bak
2409
-
2410
- // 2. Probe an arbitrary cloud URL for reachability + team status
2411
- const probe = await probeCloud('postgres://u:p@host/db');
2412
- // → { reachable: true, dialect: 'postgres', teamEnabled: false }
2413
-
2414
- // 3. Connect a fresh project to an existing cloud (auto-redeems if it's a teams DB)
2415
- const client = new TeamsClient(source);
2416
- await client.connectToExistingCloud({
2417
- label: 'atlas',
2418
- cloudUrl: 'postgres://u:p@host/db',
2419
- invite_token: 'latinv_...',
2420
- email: 'bob@example.com',
2421
- name: 'Bob',
2422
- });
2423
-
2424
- // 4. Initialize the workspace member/share machinery on a cloud DB.
2425
- // The GUI now does this automatically on migrate/connect/open; the
2426
- // helper remains for programmatic use (idempotent — a no-op if the
2427
- // cloud is already a workspace).
2428
- await client.ensureCloudWorkspaceIdentity({
2429
- label: 'atlas',
2430
- cloudUrl: 'postgres://u:p@host/db',
2431
- workspaceName: 'Atlas',
2432
- email: 'alice@example.com',
2433
- displayName: 'Alice',
2434
- });
2435
- ```
2389
+ // Probe a Postgres URL for reachability + whether it's already a Lattice cloud:
2390
+ const probe = await probeCloud('postgres://u:p@host:5432/db');
2391
+ // → { reachable: true, dialect: 'postgres', isCloud: false }
2436
2392
 
2437
- GUI consumers don't need to call these directly the Database panel surfaces `Migrate to cloud →`, and joining a shared workspace goes through `Join a team (invite)`; workspace initialization is automatic. (The standalone "Connect to existing cloud" wizard was removed in 1.16.4.)
2438
-
2439
- HTTP surface (all under `/api/dbconfig/*`, localhost-only, same auth model as the rest of `lattice gui`):
2440
-
2441
- | Method | Route | Wraps |
2442
- | ------ | ----------------------------------------- | --------------------------------------------- |
2443
- | GET | `/api/dbconfig` | returns `{ type, state, label?, host?, ... }` |
2444
- | POST | `/api/dbconfig/probe` | `probeCloud(url)` |
2445
- | POST | `/api/dbconfig/migrate-to-cloud` | `migrateLatticeData` + `archiveLocalSqlite` |
2446
- | POST | `/api/dbconfig/connect-existing` | `TeamsClient.connectToExistingCloud` |
2447
- | POST | `/api/dbconfig/save` / `connect` / `test` | unchanged from v1.12 |
2448
-
2449
- The `state` field on `GET /api/dbconfig` is one of: `local`, `team-cloud-creator`, `team-cloud-member` (the `cloud-connected` state was removed in 1.16.3; the `team-cloud-needs-invite` state was removed in 2.1.1 — a connected cloud is always a member workspace). The SPA badge color-codes them (labeled "CLOUD · OWNER / MEMBER"); the routes use them only for response shape.
2393
+ // A member joins by connecting directly as their scoped role that's all:
2394
+ const db = new Lattice('postgres://lm_bob_a91c:pw@cloud.example.com:5432/app');
2395
+ await db.init();
2396
+ const visible = await db.query('items'); // RLS-filtered to what this role may see
2397
+ ```
2398
+
2399
+ **GUI cloud endpoints** (`lattice gui`, localhost-only):
2400
+
2401
+ | Method | Route | Does |
2402
+ | ------ | -------------------------------- | ------------------------------------------------------------------ |
2403
+ | POST | `/api/dbconfig/migrate-to-cloud` | Migrate the active local Lattice into a fresh cloud (you = owner) |
2404
+ | POST | `/api/dbconfig/connect-existing` | Join a cloud directly with scoped credentials (the invite) |
2405
+ | POST | `/api/cloud/invite` | Owner provisions a scoped member role; returns the connection blob |
2406
+ | POST | `/api/cloud/share` | Owner sets a row's visibility (`private` \| `everyone`) |
2407
+ | POST | `/api/cloud/s3-config` | Owner enables S3-backed file bytes for the cloud (secret redacted) |
2408
+ | POST | `/api/cloud/system-prompt` | Owner sets the chat system prompt (owner-only to view/edit) |
2409
+
2410
+ **S3-backed file bytes (opt-in).** By default an uploaded file's bytes live only on
2411
+ the uploader's machine, so other members can see the `files` row but not fetch the
2412
+ content. Enable S3 for the cloud and uploaded bytes also go to S3 under a
2413
+ content-addressed (`<prefix>/<sha256>`) key, so any member who can SELECT the
2414
+ `files` row pulls them in the viewer. Access rides entirely on the files-row RLS;
2415
+ the bucket credential is least-privilege (`GetObject`+`PutObject`, no `ListBucket`),
2416
+ per-member and machine-local. See the caveats in [docs/cloud.md](./docs/cloud.md).
2417
+
2418
+ **Chat system prompt (owner-set).** A cloud owner can set a chat system prompt that
2419
+ is bundled into every member's assistant chat for that workspace. It's owner-only to
2420
+ view and edit (stored in `__lattice_cloud_settings`, reached via owner-gated
2421
+ `SECURITY DEFINER` functions); members never see it through the UI or API.
2422
+
2423
+ Offline editing is preserved as a **client-side local edit queue** that replays on
2424
+ reconnect — it is not tied to any replica or sync server.
2425
+
2426
+ The full architecture, the three flows in detail, the RLS / role model, the S3 +
2427
+ system-prompt designs, and the sharing API live in [docs/cloud.md](./docs/cloud.md).
2450
2428
 
2451
2429
  ---
2452
2430