latticesql 3.3.4 → 3.4.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.
@@ -608,6 +608,54 @@ const stop = await db.watch('./context', {
608
608
  stop();
609
609
  ```
610
610
 
611
+ ---
612
+
613
+ #### `reverseSyncFromFiles(outputDir, opts?): Promise<ReverseSyncResult>`
614
+
615
+ Detect external modifications to rendered context files and apply changelog-aware reverse-sync updates to the database.
616
+
617
+ ```ts
618
+ const result = await db.reverseSyncFromFiles('./context');
619
+ console.log(`Scanned ${result.filesScanned} files, changed ${result.filesChanged}`);
620
+
621
+ // With changelog-aware apply and automatic default round-trip:
622
+ await db.reverseSyncFromFiles('./context', {
623
+ apply: async (update) => {
624
+ await db.update(update.table, update.pk, update.set);
625
+ },
626
+ useDefault: true,
627
+ onSkip: (info) => {
628
+ console.log(`Skipped ${info.table}/${info.slug}/${info.filename} (custom render)`);
629
+ },
630
+ });
631
+ ```
632
+
633
+ Compares file content hashes against the current manifest, so a render-written file is recognized as an echo and skipped. Unlike `reconcile()` (which runs raw SQL), this is the changelog-aware entry point used by the GUI file-loopback: pass `apply` to route each update through a versioned write (so a file edit lands in the changelog and shows up live, just like a GUI edit), and `useDefault` to round-trip structured frontmatter + body `key: value` fields for files without a hand-written `reverseSync` function. A file whose changes can't be safely parsed (free-form/custom render) is surfaced via `onSkip` rather than guessed at, so a lossy render can't corrupt a row.
634
+
635
+ **`ReverseSyncProcessOptions`** (all optional):
636
+
637
+ | Option | Type | Description |
638
+ | ------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- |
639
+ | `apply` | `(update: ReverseSyncUpdate) => Promise<void>` | Route each update through a changelog-aware path instead of raw SQL |
640
+ | `useDefault` | `boolean` | Derive updates for files lacking a hand-written `reverseSync` (frontmatter + body pairs) |
641
+ | `onSkip` | `(info: { table; slug; filename; filePath }) => void` | Called when a changed file produced no importable update (free-form/custom render) |
642
+
643
+ The result reports `filesScanned`, `filesChanged`, `updatesApplied`, and any per-file `errors`.
644
+
645
+ ---
646
+
647
+ #### `rebuildFtsIndexes(): Promise<void>`
648
+
649
+ (Re)build the full-text search indexes for all tables with `fts` configuration, backfilling existing rows. Used after `migrate-to-cloud` to ensure search works on a migrated cloud workspace.
650
+
651
+ ```ts
652
+ await db.rebuildFtsIndexes();
653
+ ```
654
+
655
+ The `migrate-to-cloud` command calls this automatically after copying rows; use it manually to recover search if an index was omitted or needs rebuilding. Idempotent — running it multiple times is safe.
656
+
657
+ ---
658
+
611
659
  **`WatchOptions`:**
612
660
 
613
661
  | Option | Type | Default | Description |
package/docs/assistant.md CHANGED
@@ -43,6 +43,14 @@ in the GUI via the same mode-aware navigator the activity feed uses; it links th
43
43
  user-facing record (the contract/person/etc.) rather than an internal `files` id,
44
44
  and only ids it actually retrieved.
45
45
 
46
+ **The assistant reads your organized context.** A new `get_row_context` tool lets
47
+ the assistant pull a record's full rendered context — its own fields, related
48
+ records, and combined summary — in a single call. It leverages the context tree
49
+ Lattice maintains rather than re-stitching together raw reads, so the model can
50
+ answer follow-ups like "summarize this record" or "what are the related items?" in
51
+ one tool call. It falls back to direct row tools when a record hasn't been rendered
52
+ yet.
53
+
46
54
  **Deleting a table is guarded + reversible.** The `delete_entity` tool refuses
47
55
  built-in tables, tables another table links to, and tables you don't own. An
48
56
  **empty** table is soft-deleted immediately; a **non-empty** one is **not**
@@ -51,6 +59,12 @@ count and the assistant asks, then you choose `delete_data` (soft-delete the row
51
59
  too) or `move_to` another table. The physical table + rows are kept (no hard
52
60
  drop), so the whole thing is revertible from version history.
53
61
 
62
+ **Adding a field to an existing table.** The `add_column` tool lets the assistant
63
+ add a single column to an existing table on request ("add a priority field to
64
+ projects", "add an email column"). The column is registered live, persisted,
65
+ audited, and revertible. On a cloud, the per-column masking view is rebuilt so
66
+ members see the new field immediately.
67
+
54
68
  Conversations persist in the native `chat_threads` / `chat_messages` entities;
55
69
  use the thread switcher to revisit them. A new thread is **named from a short AI
56
70
  summary** of its first exchange (e.g. "Adding New Notes About Cheese"). The
@@ -74,6 +88,13 @@ open, the chat passes that record (table + id) as context, so "delete this file"
74
88
  one. It's a hint only — every action still goes through the same permission-gated
75
89
  tools, so it can't reach a record you couldn't otherwise touch.
76
90
 
91
+ **Pasted GUI links resolve to the actual record.** When you paste a local GUI link
92
+ (the address bar's `…/#/fs/<table>/<id>`) into the chat, the assistant resolves it
93
+ deterministically to its real data in the database (via the same permission-gated
94
+ read as any other access), so it can answer queries about that record without
95
+ needing to fetch or guess. Resolution happens in code; the resolved data appears in
96
+ context alongside the viewed record.
97
+
77
98
  The assistant can also **answer questions about Lattice itself.** Ask "what is
78
99
  private mode?" or "how do I invite a member?" and it calls the `lattice_help` tool,
79
100
  which searches Lattice's own documentation (these `docs/*.md` files — the single
package/docs/cloud.md CHANGED
@@ -158,6 +158,10 @@ Lattice only needs the roles to exist and to be members of `lattice_members`.
158
158
 
159
159
  ---
160
160
 
161
+ ### Cloud sharing internals consolidated (v3.4)
162
+
163
+ The internal machinery backing row sharing was refactored in v3.4 with **no behavior change** to the live features: row `private` / `everyone` / custom "specific people" sharing, table `default_row_visibility` and `never_share`, and the `owner` secret-column mask all work identically. The consolidation removed unreachable masking machinery and moved permission checks into single `SECURITY DEFINER` helpers, eliminating the risk of regressions when sharing logic changes. The changes are additive and idempotent — clouds converge safely on an owner's next open.
164
+
161
165
  ## Sharing: private by default
162
166
 
163
167
  Every row is **private to its owner** the moment it's written — the per-table
@@ -236,6 +240,22 @@ now lives canonically in the DB and the mask view regenerates from it on change.
236
240
 
237
241
  ---
238
242
 
243
+ ## Opening the cloud & the converge
244
+
245
+ When any member opens a cloud, Lattice runs a **converge** pass: it reads the current Postgres schema against the workspace's registered entities and reconciles them — granting table/column privileges to the member group, rebuilding masking views, installing any missing RLS machinery. Since v3.4, the converge is **per-table fault-isolated**: if the connecting role cannot `ALTER` or `GRANT` a table (most often because it was created by a different Postgres role), that one table is skipped with an actionable reason instead of failing the whole workspace and degrading all objects to "Failed to fetch". The skip is reported in `GET /api/dbconfig` as `convergeWarnings`, for example: `"owned by role X, but this workspace connects as Y — fix with: `ALTER TABLE … OWNER TO Y`"`.
246
+
247
+ `POST /api/workspaces/reload` re-reads the config and re-registers entities in place without restarting the GUI, so a table added out-of-band surfaces immediately.
248
+
249
+ ### Plaintext database URL heal-on-open
250
+
251
+ If a workspace config stores a raw `postgres://…` connection string (with its password in cleartext) in the `db:` line — typically from an older workspace or a migration — opening it now automatically moves the URL into the encrypted credential store and rewrites the line to a `${LATTICE_DB:<label>}` reference. This is idempotent: configs already using a reference, or pointing to a SQLite file, are untouched. An existing credential is never overwritten, so the operation is safe to repeat.
252
+
253
+ ## Rendered context scoped to viewer
254
+
255
+ On a cloud, the background render now reads every table **through the member's row-level-security connection and through the per-column masking view**. The rendered markdown a member's assistant reads off disk contains only the rows they may see (per RLS), with owner-only columns blanked, and with any per-viewer enrichment values they're allowed to see folded in. When sharing changes — a row is shared or un-shared — the affected member's context tree re-renders promptly, so it never lingers on a stale view.
256
+
257
+ Owners and local single-user workspaces render the full tree unchanged, as before.
258
+
239
259
  ## The three user flows
240
260
 
241
261
  There are exactly three things you do with a cloud: **migrate** into one, **join**
@@ -67,6 +67,19 @@ join one. Creating/joining switches into the new workspace; the normal layout
67
67
  returns on reload. The last workspace can now be deleted (it drops you back to the
68
68
  welcome screen rather than being refused).
69
69
 
70
+ ## Seamless GUI auto-update (3.4)
71
+
72
+ When `lattice gui` is launched from a published install (global or project-local npm install), it runs as a small supervisor that silently installs the latest published version before opening the browser. While you work, the supervisor keeps checking for updates in the background; when a new version lands it installs it and relaunches the server on the same port. The open tab reconnects, notices the version changed, and reloads onto the new build — **no manual refresh, no reinstall**.
73
+
74
+ A git checkout or `npx` copy is left untouched (auto-update is disabled there); a failed install surfaces in the GUI rather than being swallowed.
75
+
76
+ **HTTP endpoints** (for polling / UI integration):
77
+
78
+ | Route | Method | Returns |
79
+ | -------------------- | ------ | ------------------------- |
80
+ | `/api/version` | GET | `{ version: string }` |
81
+ | `/api/update/status` | GET | Update state and progress |
82
+
70
83
  ## Auto-render (SQL → markdown)
71
84
 
72
85
  `enableAutoRender(outputDir)` debounces a re-render on every
@@ -79,6 +92,26 @@ A bare `new Lattice(path)` does **not** auto-render (`_scheduleAutoRender`
79
92
  early-returns when no output dir is set) — call `render(dir)` / `reconcile(dir)`
80
93
  manually, or opt in with `enableAutoRender(dir)`.
81
94
 
95
+ ## File loopback (3.4)
96
+
97
+ When the GUI is serving a workspace, editing a rendered `.md` file on disk is automatically captured back into the database through the normal write path — so the change lands in the changelog (versioned/undoable) and appears live in the GUI, exactly as if the edit had been made there. Structured frontmatter and body `key: value` fields round-trip automatically; edits that can't be safely parsed (free-form or custom renders) are surfaced as a notice rather than guessed at, so a lossy render can't corrupt a row. Render echoes are suppressed via the manifest, so there is no write loop.
98
+
99
+ **For embedders**, `reverseSyncFromFiles()` exposes the same changelog-aware reverse-sync the GUI loopback uses:
100
+
101
+ ```ts
102
+ import { Lattice } from 'latticesql';
103
+
104
+ const db = new Lattice(config);
105
+ await db.init();
106
+
107
+ // Round-trip frontmatter + body `key: value` edits from the rendered tree
108
+ // back into the DB. Pass `apply` to route each update through a versioned
109
+ // write (so a file edit is recorded exactly like a GUI edit).
110
+ const result = await db.reverseSyncFromFiles('./context', { useDefault: true });
111
+ ```
112
+
113
+ `reverseSyncFromFiles(outputDir, opts)` compares file hashes against the current manifest (so a render-written file is recognized as an echo and skipped), parses the changed files, applies the updates, and returns a summary of what was applied.
114
+
82
115
  The canonical `Context/` layout is DB-aligned and zero-config: table → folder,
83
116
  row → subfolder, `<ENTITY>.md` plus relation rollups, derived from the schema
84
117
  via `deriveCanonicalContexts`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latticesql",
3
- "version": "3.3.4",
3
+ "version": "3.4.0",
4
4
  "description": "Persistent structured memory for AI agent systems — pluggable SQLite or Postgres backend, LLM context bridge",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",