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/dist/index.d.ts CHANGED
@@ -766,6 +766,31 @@ interface ChangelogOptions {
766
766
  /** Keep at most this many entries per row. */
767
767
  maxEntriesPerRow?: number;
768
768
  }
769
+ /**
770
+ * Provenance for a change — the per-viewer observation model's audit metadata.
771
+ * Every field is optional and additive: a plain edit carries none of it and
772
+ * behaves exactly as before. A *derived* value (e.g. an AI enrichment computed
773
+ * from source files) carries the `sourceRef` set that informed it, so the
774
+ * change-log records which authority produced the value rather than discarding
775
+ * it (the confused-deputy guard). Stage-0 persists this as audit metadata;
776
+ * later stages read it to fold per-viewer entities and cascade revocation.
777
+ */
778
+ interface ChangeProvenance {
779
+ /** Source row/file id(s) that informed this value. Persisted as a JSON array
780
+ * in `__lattice_changelog.source_ref`. NOT a foreign key — purely an audit
781
+ * trail; a source may be deleted without violating anything. */
782
+ sourceRef?: string[] | string;
783
+ /** `ground_truth` — a direct edit; `derived` — computed from sources. */
784
+ changeKind?: 'ground_truth' | 'derived';
785
+ /** Reserved per-value audience. Omitted ⇒ the row's audience (no change). */
786
+ audience?: string;
787
+ /** Marks the source(s) sensitive (a future crypto-shred candidate). */
788
+ sourceSensitive?: boolean;
789
+ /** A prior changelog id this entry supersedes. */
790
+ supersededBy?: string;
791
+ /** Free-text reason (mirrors the legacy `reason` field). */
792
+ reason?: string;
793
+ }
769
794
  /**
770
795
  * A single entry in the change log returned by `history()` and
771
796
  * `recentChanges()`.
@@ -789,6 +814,11 @@ interface ChangeEntry {
789
814
  reason: string | null;
790
815
  /** ISO timestamp of when the change was recorded. */
791
816
  createdAt: string;
817
+ /** Source-set that informed a derived value (deserialized from `source_ref`).
818
+ * Null for plain edits + rows written before 3.0. */
819
+ sourceRef?: string[] | null;
820
+ /** `ground_truth` | `derived` provenance tag (null when unrecorded). */
821
+ changeKind?: 'ground_truth' | 'derived' | null;
792
822
  }
793
823
  interface SecurityOptions {
794
824
  sanitize?: boolean;
@@ -932,6 +962,15 @@ interface TableDefinition {
932
962
  * directly in code via `define()`.
933
963
  */
934
964
  fieldTypes?: Record<string, string>;
965
+ /**
966
+ * Column name → audience identifier (Stage-0 per-viewer enrichment
967
+ * scaffolding). A column absent from this map has `row-audience` — visible to
968
+ * whoever can see the row, i.e. today's behavior. Populated from YAML field
969
+ * `audience:` specs (or a future code-level option). Recorded by the schema
970
+ * manager so a later stage can generate a per-column cell-masking view from
971
+ * it; unused in Stage-0, so it never changes behavior.
972
+ */
973
+ columnAudience?: Record<string, string>;
935
974
  /**
936
975
  * Optional human description of what this entity represents. Surfaced in the
937
976
  * GUI and given to the assistant's ingest classifier so it can decide which
@@ -1275,6 +1314,16 @@ interface CountOptions {
1275
1314
  }
1276
1315
  interface InitOptions {
1277
1316
  migrations?: Migration[];
1317
+ /**
1318
+ * Open an already-provisioned database WITHOUT issuing any DDL — no
1319
+ * `CREATE TABLE`, no migrations, no FTS/changelog/embeddings setup. Used to
1320
+ * connect as a scoped, non-superuser cloud member: every table, migration,
1321
+ * and policy was installed by the cloud owner, and the member's role has no
1322
+ * CREATE/ALTER privilege, so applying the schema would fail. Declared tables
1323
+ * are introspected (best-effort) to populate the column cache; tables the
1324
+ * member can't see are skipped.
1325
+ */
1326
+ introspectOnly?: boolean;
1278
1327
  }
1279
1328
  interface Migration {
1280
1329
  version: number | string;
@@ -1624,6 +1673,147 @@ interface ReconcileResult extends RenderResult {
1624
1673
  reverseSeedRequired: ReverseSeedDetection[];
1625
1674
  }
1626
1675
 
1676
+ /**
1677
+ * Cryptographic erasure ("crypto-shred") for sources flagged sensitive.
1678
+ *
1679
+ * Un-sharing or deleting a source already removes its derived values from every
1680
+ * viewer's fold (the fold only includes observations whose sources are visible —
1681
+ * see ./fold.ts). But the bytes can linger in backups and WAL. For a legal /
1682
+ * GDPR "right to be forgotten", a source flagged `source_sensitive` gets a
1683
+ * stronger guarantee: every value derived from it is stored ENCRYPTED under a
1684
+ * key unique to that source. To forget the source you destroy its key — and the
1685
+ * derived values become unrecoverable everywhere the ciphertext exists, backups
1686
+ * included, because the key never lived in the row.
1687
+ *
1688
+ * The key store is pluggable so the keys can live somewhere shred-durable and
1689
+ * separate from the data (a KMS, a key table on a different retention policy).
1690
+ * The default in-memory store is for tests + single-process use.
1691
+ */
1692
+ /** Holds the per-source AES keys. Destroying a key is the erasure operation. */
1693
+ interface SourceKeyStore {
1694
+ /** The key for a source, or undefined if it was never created / has been shredded. */
1695
+ get(sourceId: string): Buffer | undefined;
1696
+ /** The key for a source, creating a fresh random 256-bit key on first use. */
1697
+ getOrCreate(sourceId: string): Buffer;
1698
+ /** Destroy a source's key — irreversibly. Values sealed under it can no longer
1699
+ * be opened, anywhere. Idempotent. */
1700
+ destroy(sourceId: string): void;
1701
+ }
1702
+ /** In-memory {@link SourceKeyStore}. Keys vanish with the process — fine for
1703
+ * tests; production should persist keys in a shred-durable store. */
1704
+ declare class InMemorySourceKeyStore implements SourceKeyStore {
1705
+ private readonly keys;
1706
+ get(sourceId: string): Buffer | undefined;
1707
+ getOrCreate(sourceId: string): Buffer;
1708
+ destroy(sourceId: string): void;
1709
+ }
1710
+ /** Error thrown when opening a value whose source key has been shredded. */
1711
+ declare class SourceShreddedError extends Error {
1712
+ readonly sourceId: string;
1713
+ constructor(sourceId: string);
1714
+ }
1715
+ /**
1716
+ * Encrypt a derived value under its (sensitive) source's key, creating the key on
1717
+ * first use. The returned ciphertext is opaque without the source key.
1718
+ */
1719
+ declare function sealUnderSource(plaintext: string, sourceId: string, store: SourceKeyStore): string;
1720
+ /**
1721
+ * Decrypt a value sealed under a source's key. Throws {@link SourceShreddedError}
1722
+ * if the source has been shredded (the key is gone) — the value is unrecoverable,
1723
+ * which is the intended "forgotten" state, not an error to paper over.
1724
+ */
1725
+ declare function openUnderSource(ciphertext: string, sourceId: string, store: SourceKeyStore): string;
1726
+ /**
1727
+ * Cryptographically shred a source: destroy its key so every value sealed under
1728
+ * it is unrecoverable everywhere the ciphertext exists (live rows, backups, WAL).
1729
+ * Pair with the fold-level revocation (which removes the value from live views) —
1730
+ * this is the durable, backup-proof half for legally-sensitive sources.
1731
+ */
1732
+ declare function shredSource(sourceId: string, store: SourceKeyStore): void;
1733
+
1734
+ /**
1735
+ * Progress reporting for the render engine.
1736
+ *
1737
+ * A render walks every table and every per-entity context file; for a large
1738
+ * database this can take a while. These types let a caller observe progress
1739
+ * (per-table %, which table is in flight) and cancel a render in progress via
1740
+ * an `AbortSignal`. All of it is optional: a render with no `onProgress` and no
1741
+ * `signal` behaves exactly as it did before — zero overhead, identical output.
1742
+ */
1743
+ /** The kind of progress event the render engine emits. */
1744
+ type RenderProgressKind = 'table-start' | 'table-progress' | 'table-done' | 'done' | 'error';
1745
+ /**
1746
+ * A single progress event. Fields beyond `kind` describe the table currently
1747
+ * being rendered (`table`, `tableIndex`, `tableCount`) and how far along it is
1748
+ * (`entitiesRendered`, `entitiesTotal`, `pct`). `durationMs` is set on the
1749
+ * terminal `done` event; `message` carries human-readable detail (e.g. the
1750
+ * error text on an `error` event).
1751
+ */
1752
+ interface RenderProgress {
1753
+ /** Discriminator: what stage of the render this event reports. */
1754
+ kind: RenderProgressKind;
1755
+ /** The table being rendered, or null for non-table events (`done`/`error`). */
1756
+ table: string | null;
1757
+ /** Entities rendered so far within `table` (per-table running count). */
1758
+ entitiesRendered: number;
1759
+ /** Total entities in `table` — the denominator for the per-table %. */
1760
+ entitiesTotal: number;
1761
+ /** Zero-based index of `table` among the entity-context tables. */
1762
+ tableIndex: number;
1763
+ /** Total number of entity-context tables in this render. */
1764
+ tableCount: number;
1765
+ /** Per-table completion percentage, 0–100, exact (`rendered/total`). */
1766
+ pct: number;
1767
+ /** Wall-clock duration of the whole render, set on the `done` event. */
1768
+ durationMs?: number;
1769
+ /** Human-readable detail; the error text on an `error` event. */
1770
+ message?: string;
1771
+ }
1772
+ /** Sink the render engine pushes {@link RenderProgress} events into. */
1773
+ type RenderProgressCallback = (event: RenderProgress) => void;
1774
+ /**
1775
+ * Optional knobs for a render. Both are opt-in:
1776
+ * - `onProgress` — observe per-table render progress.
1777
+ * - `signal` — cancel a render in flight; the engine bails between entities and
1778
+ * returns the partial manifest (which the caller is expected to discard).
1779
+ */
1780
+ interface RenderOptions {
1781
+ onProgress?: RenderProgressCallback;
1782
+ signal?: AbortSignal;
1783
+ }
1784
+ /**
1785
+ * Coalesces high-frequency `table-progress` events down to ≤ ~5/sec per table,
1786
+ * while always passing through the lifecycle events (`table-start`,
1787
+ * `table-done`, `done`, `error`) immediately.
1788
+ *
1789
+ * A render over a 6,760-row table would otherwise emit thousands of
1790
+ * `table-progress` events; this caps it at a few dozen. The throttle lives in
1791
+ * the engine so every consumer benefits and no per-entity object crosses the
1792
+ * progress boundary more than ~5×/sec.
1793
+ *
1794
+ * The 200 ms window is reset on every `table-start` (via {@link force}), so each
1795
+ * table gets its own fresh budget and the first progress tick of a new table is
1796
+ * not suppressed by the previous table's last tick.
1797
+ */
1798
+ declare class ProgressThrottle {
1799
+ private readonly cb;
1800
+ private readonly windowMs;
1801
+ private lastEmit;
1802
+ constructor(cb: RenderProgressCallback | undefined, windowMs?: number);
1803
+ /**
1804
+ * Emit a `table-progress` event, but only if the window since the last
1805
+ * passthrough has elapsed. Dropped events are simply not delivered — the next
1806
+ * one that survives carries the latest running count.
1807
+ */
1808
+ tick(event: RenderProgress): void;
1809
+ /**
1810
+ * Emit a lifecycle event immediately and reset the throttle window. Use for
1811
+ * `table-start`, `table-done`, `done`, and `error` — none of which should
1812
+ * ever be dropped. Resetting on `table-start` gives each table a clean budget.
1813
+ */
1814
+ force(event: RenderProgress): void;
1815
+ }
1816
+
1627
1817
  /**
1628
1818
  * Initialise Lattice from a YAML config file instead of an explicit path.
1629
1819
  *
@@ -1721,8 +1911,8 @@ declare class Lattice {
1721
1911
  * Idempotent: a second call for an already-registered table is a no-op
1722
1912
  * (the underlying CREATE TABLE IF NOT EXISTS is already idempotent at
1723
1913
  * the DB level; this skip avoids the SchemaManager.define throw on
1724
- * re-registration). Use this property for clients (e.g. TeamsClient)
1725
- * that may bootstrap their internal tables on every session start.
1914
+ * re-registration). Useful for callers that may bootstrap their
1915
+ * internal tables on every session start.
1726
1916
  *
1727
1917
  * Throws if called before `init()` (use `define()` instead).
1728
1918
  */
@@ -1793,6 +1983,13 @@ declare class Lattice {
1793
1983
  * SchemaManager default.
1794
1984
  */
1795
1985
  getPrimaryKey(table: string): string[];
1986
+ /**
1987
+ * Per-column audience for a table (per-viewer enrichment) — column name →
1988
+ * audience identifier. A column absent from the map has `row-audience`
1989
+ * (visible to whoever can see the row). Empty until a column declares
1990
+ * `audience:`. Drives the generated cell-masking view.
1991
+ */
1992
+ getColumnAudience(table: string): Record<string, string>;
1796
1993
  /**
1797
1994
  * Return the raw column declarations for a registered table, as
1798
1995
  * passed to `define()` / `defineLate()`. Returns null for tables
@@ -1879,7 +2076,35 @@ declare class Lattice {
1879
2076
  * names containing quotes, semicolons, whitespace, etc.
1880
2077
  */
1881
2078
  private _assertIdent;
1882
- insert(table: string, row: Row): Promise<string>;
2079
+ insert(table: string, row: Row, provenance?: ChangeProvenance): Promise<string>;
2080
+ /**
2081
+ * Insert a row while atomically forcing its cloud row-visibility, regardless of
2082
+ * the table's `default_row_visibility`. The per-table insert trigger reads a
2083
+ * transaction-local GUC (`lattice.force_row_visibility`); we set it and run the
2084
+ * INSERT inside a single transaction, so the row is stamped at `visibility` the
2085
+ * instant it exists — it is never momentarily visible at the table default, and
2086
+ * the change-feed `NOTIFY` (delivered only at COMMIT) fires when the row already
2087
+ * carries this visibility. This closes the create-then-demote window that a
2088
+ * plain `insert()` + `setRowVisibility()` would leave open.
2089
+ *
2090
+ * Postgres-only: SQLite is single-user (no cross-viewer leak) and has no trigger
2091
+ * to read the GUC, so it degrades to a plain {@link insert}. A `never_share`
2092
+ * table still wins — its rows are forced private even if `visibility` is
2093
+ * `'everyone'` (the trigger enforces that precedence).
2094
+ *
2095
+ * @since 3.1.0
2096
+ */
2097
+ insertForcingVisibility(table: string, row: Row, visibility: 'private' | 'everyone', provenance?: ChangeProvenance): Promise<string>;
2098
+ /**
2099
+ * Build the INSERT statement + canonical pk for a row (sanitize → schema-filter →
2100
+ * auto-pk → encrypt). Shared by {@link insert} and {@link insertForcingVisibility}
2101
+ * so both produce byte-identical writes; the latter only differs in running it
2102
+ * inside a GUC-scoped transaction.
2103
+ */
2104
+ private _prepareInsert;
2105
+ /** Post-insert side effects (changelog, audit, write hooks, embedding sync),
2106
+ * identical for the plain and force-visibility insert paths. */
2107
+ private _afterInsert;
1883
2108
  /**
1884
2109
  * Insert a row and return the full inserted row (including auto-generated
1885
2110
  * fields and defaults). Equivalent to `insert()` followed by `get()`.
@@ -1889,7 +2114,7 @@ declare class Lattice {
1889
2114
  insertReturning(table: string, row: Row): Promise<Row>;
1890
2115
  upsert(table: string, row: Row): Promise<string>;
1891
2116
  upsertBy(table: string, col: string, val: unknown, row: Row): Promise<string>;
1892
- update(table: string, id: PkLookup, row: Partial<Row>): Promise<void>;
2117
+ update(table: string, id: PkLookup, row: Partial<Row>, provenance?: ChangeProvenance): Promise<void>;
1893
2118
  /**
1894
2119
  * Update a row and return the full updated row. Equivalent to `update()`
1895
2120
  * followed by `get()`.
@@ -1897,7 +2122,43 @@ declare class Lattice {
1897
2122
  * @since 0.17.0
1898
2123
  */
1899
2124
  updateReturning(table: string, id: PkLookup, row: Partial<Row>): Promise<Row>;
1900
- delete(table: string, id: PkLookup): Promise<void>;
2125
+ delete(table: string, id: PkLookup, provenance?: ChangeProvenance): Promise<void>;
2126
+ /**
2127
+ * Record a DERIVED observation about a row WITHOUT mutating the canonical row.
2128
+ * The canonical row stays broadly-visible ground truth; the observation carries
2129
+ * its provenance (the source-set it was derived from) and is folded into a
2130
+ * per-viewer entity at read time by {@link foldForViewer} — visible only to a
2131
+ * viewer who can reach every one of its sources. This is how an AI enrichment
2132
+ * lands a per-viewer value without leaking it into the shared row, and without
2133
+ * moving the row's `updated_at` (so a viewer who can't see the source can't even
2134
+ * detect that the enrichment exists). `changes` maps column → derived value.
2135
+ */
2136
+ /** Ensure the observation substrate (`__lattice_changelog`) exists. Cloud setup
2137
+ * calls this before `enableChangelogRls` so the table is present to secure
2138
+ * even if nothing has written an observation yet. Idempotent. */
2139
+ ensureObservationSubstrate(): Promise<void>;
2140
+ observe(table: string, id: PkLookup, changes: Record<string, unknown>, provenance?: ChangeProvenance, opts?: {
2141
+ keyStore?: SourceKeyStore;
2142
+ }): Promise<void>;
2143
+ /**
2144
+ * Compile the per-viewer view of a row: the ground-truth canonical row with the
2145
+ * DERIVED observations the viewer is allowed to see folded on top (latest
2146
+ * audience-visible observation per attribute wins). A derived value is visible
2147
+ * only when the viewer can reach every source it came from, so un-sharing a
2148
+ * source reverts the value with no residue. `visibleSources` is the set of
2149
+ * source ids the viewer can see; omit it (or pass `'all'`) for the local
2150
+ * single-user case where you see everything. Returns null if the row is absent.
2151
+ */
2152
+ foldForViewer(table: string, id: PkLookup, opts?: {
2153
+ visibleSources?: Iterable<string> | 'all';
2154
+ keyStore?: SourceKeyStore;
2155
+ }): Promise<Row | null>;
2156
+ /** Open any crypto-sealed values in a derived observation's `changes`. Returns
2157
+ * the plaintext changes, or `null` if a sealed value can't be opened because
2158
+ * its source's key was shredded (the value is gone for good). Values that
2159
+ * aren't sealed pass through; with no key store, sealed values can't be read,
2160
+ * so the observation is dropped (returns null). */
2161
+ private _openSealedObservation;
1901
2162
  get(table: string, id: PkLookup): Promise<Row | null>;
1902
2163
  /**
1903
2164
  * Upsert a record by natural key. If a non-deleted record with the given
@@ -1969,78 +2230,23 @@ declare class Lattice {
1969
2230
  */
1970
2231
  search(table: string, query: string, opts?: SearchOptions): Promise<SearchResult[]>;
1971
2232
  query(table: string, opts?: QueryOptions): Promise<Row[]>;
2233
+ count(table: string, opts?: CountOptions): Promise<number>;
2234
+ render(outputDir: string, opts?: RenderOptions): Promise<RenderResult>;
1972
2235
  /**
1973
- * Row-level-security list read for Lattice Teams (2.2). Returns only the
1974
- * rows of `table` that `userId` may see in team `teamId`, evaluated
1975
- * entirely in SQL (indexed, bounded — never "load every row then filter
1976
- * in JS"). A row is visible iff it has a `__lattice_row_acl` entry owned by
1977
- * the user or marked 'everyone', or a 'custom' entry with a matching
1978
- * `__lattice_row_grants` row, OR it has no ACL entry at all and the caller
1979
- * passes `noAclVisible` (the table default is 'everyone', or the user owns
1980
- * the table — the pre-2.2 / never-narrowed case). Soft-deleted rows are
1981
- * excluded by default; results reuse the same decrypt path as `query()`.
2236
+ * Render into `outputDir` through the shared single-flight guard, intended to
2237
+ * be called fire-and-forget (e.g. the GUI's instant-open background render).
1982
2238
  *
1983
- * The ACL predicate joins on the table's primary-key column cast to TEXT
1984
- * (ACL pks are stored as TEXT), so it is correct regardless of the user
1985
- * table's pk type and works on both SQLite and Postgres. The teams layer's
1986
- * `listVisibleRows` (src/teams/row-access.ts) is the intended caller.
1987
- */
1988
- queryVisible(table: string, opts: {
1989
- teamId: string;
1990
- userId: string;
1991
- /**
1992
- * Whether rows with NO `__lattice_row_acl` entry are visible to this
1993
- * user true when the table default is 'everyone' OR the user owns the
1994
- * table (the pre-2.2 / never-narrowed case). Resolved by the teams layer
1995
- * (`listVisibleRows`); defaults to false, i.e. only rows with an explicit
1996
- * ACL entry granting access are returned.
1997
- */
1998
- noAclVisible?: boolean;
1999
- /** Soft-delete handling: 'exclude' (default), 'only' (trash), 'any'. */
2000
- deleted?: 'exclude' | 'only' | 'any';
2001
- limit?: number;
2002
- offset?: number;
2003
- orderBy?: string;
2004
- orderDir?: 'asc' | 'desc';
2005
- }): Promise<Row[]>;
2006
- /**
2007
- * Visible-row counts for MANY tables in a single round-trip, using the same
2008
- * ACL predicate as {@link queryVisible} — so dashboard tiles agree with what
2009
- * the rows view lists and a physical count never reveals the existence or
2010
- * volume of rows the user can't see. One aggregated
2011
- * `SELECT (SELECT COUNT(*) …) AS c0, …` statement (no per-table fan-out, so
2012
- * a session pooler with few slots survives concurrent refreshes), capped at
2013
- * 50 tables per pass; overflow is logged and skipped (no silent truncation)
2014
- * and those tables count as absent — the caller renders "—". Soft-deleted
2015
- * rows are excluded wherever the table carries `deleted_at`, matching the
2016
- * default rows view.
2017
- */
2018
- countVisibleMany(specs: {
2019
- table: string;
2020
- noAclVisible: boolean;
2021
- }[], opts: {
2022
- teamId: string;
2023
- userId: string;
2024
- }): Promise<Map<string, number>>;
2025
- /**
2026
- * Hosted-sync change-log pull, filtered per recipient for 2.2 row-level
2027
- * security (the hosted server's sole enforcement mechanism). Returns
2028
- * `__lattice_change_log` rows with seq > `since` for team `teamId` that
2029
- * `userId` is permitted to receive:
2030
- * - targeted envelopes (`recipient_user_id = userId`), plus
2031
- * - broadcast envelopes (`recipient_user_id IS NULL`) that are either
2032
- * table-level (`pk IS NULL` — schema / unshare, delivered to all) or
2033
- * whose row is currently visible to the user via `__lattice_row_acl` /
2034
- * `__lattice_row_grants` (or has no ACL entry and the table defaults to
2035
- * 'everyone').
2036
- * Ordered by seq, capped at `limit`. Raw SQL because the predicate needs
2037
- * OR / EXISTS that the `query()` API can't express; bounded by the seq
2038
- * window and indexed ACL point-lookups. Mirrors {@link queryVisible}'s
2039
- * visibility logic so a member never pulls the bytes of a row they can't see.
2040
- */
2041
- listChangesForRecipient(teamId: string, since: number, userId: string, limit: number): Promise<Row[]>;
2042
- count(table: string, opts?: CountOptions): Promise<number>;
2043
- render(outputDir: string): Promise<RenderResult>;
2239
+ * The guard ({@link _renderGuarded}) holds {@link _autoRenderInFlight} for the
2240
+ * render's duration, so a data mutation that lands while this render is in
2241
+ * flight is deferred by {@link _runAutoRender} and coalesced when this
2242
+ * render settles, `finally` clears the flag and re-arms exactly one follow-up
2243
+ * render via {@link _rearmAutoRenderIfPending}. Net invariant: at most one
2244
+ * render to a given dir at a time.
2245
+ *
2246
+ * Errors propagate to the caller (the GUI surfaces them, never silently swallowed); they are
2247
+ * not swallowed here.
2248
+ */
2249
+ renderInBackground(outputDir: string, opts?: RenderOptions): Promise<RenderResult>;
2044
2250
  sync(outputDir: string): Promise<SyncResult>;
2045
2251
  /**
2046
2252
  * Recover rows from rendered files into empty database tables.
@@ -2156,6 +2362,17 @@ declare class Lattice {
2156
2362
  /** Turn off automatic rendering and cancel any pending render. */
2157
2363
  disableAutoRender(): this;
2158
2364
  private _scheduleAutoRender;
2365
+ /**
2366
+ * Shared single-flight render path used by {@link renderInBackground}.
2367
+ *
2368
+ * Holds {@link _autoRenderInFlight} for the render's duration so the
2369
+ * mutation-driven {@link _runAutoRender} defers while this render runs (it
2370
+ * sees the flag and marks itself pending instead of starting a second,
2371
+ * overlapping render). On settle, `finally` clears the flag and re-arms a
2372
+ * single coalesced follow-up render if any mutation arrived mid-flight.
2373
+ * Errors propagate to the caller; the flag is always cleared.
2374
+ */
2375
+ private _renderGuarded;
2159
2376
  private _runAutoRender;
2160
2377
  private _rearmAutoRenderIfPending;
2161
2378
  /**
@@ -2167,10 +2384,31 @@ declare class Lattice {
2167
2384
  * shouldn't block the write completion.
2168
2385
  */
2169
2386
  private _syncEmbedding;
2170
- /** Create the __lattice_changelog table and index. */
2387
+ /**
2388
+ * Create the __lattice_changelog table and index. This is the single,
2389
+ * canonical change-log substrate (the dead `__lattice_change_log` team-sync
2390
+ * envelope was removed in 3.0). Beyond the field-level delta columns it
2391
+ * carries provenance columns for the per-viewer observation model:
2392
+ * `source_ref` (the source-set that informed a derived value),
2393
+ * `change_kind` (`ground_truth` | `derived`), `superseded_by`, `audience`
2394
+ * (defaults to row audience), and `source_sensitive` (crypto-shred flag).
2395
+ * All are additive + nullable (or defaulted) — Stage-0 metadata, no behavior
2396
+ * change until later stages read them.
2397
+ */
2398
+ /** Whether `__lattice_changelog` physically exists (read-only; no DDL), so a
2399
+ * scoped member can decide there are no observations without trying to create
2400
+ * the table. */
2401
+ private _changelogTableExists;
2171
2402
  private _ensureChangelogTable;
2172
- /** Append a changelog entry if the table has changelog enabled. */
2403
+ /** Append a changelog entry if the table has changelog enabled. The optional
2404
+ * `prov` carries the per-viewer observation provenance (source-set, kind,
2405
+ * audience, …); when omitted the entry behaves exactly as a pre-3.0 entry. */
2173
2406
  private _appendChangelog;
2407
+ /** The ungated change-log INSERT. `_appendChangelog` wraps it with the
2408
+ * changelog-enabled gate; `observe()` calls it directly (an observation is an
2409
+ * explicit, always-recorded write to the substrate). The change-log table must
2410
+ * exist already. */
2411
+ private _writeChangelogRow;
2174
2412
  /** Prune changelog entries based on retention policy. */
2175
2413
  private _pruneChangelog;
2176
2414
  /** Parse a raw changelog DB row into a ChangeEntry. */
@@ -2286,6 +2524,15 @@ interface LatticeFieldDef {
2286
2524
  * (e.g. `assignee_id: { ref: user }` → relation name `assignee`).
2287
2525
  */
2288
2526
  ref?: string;
2527
+ /**
2528
+ * Per-column audience (Stage-0 scaffolding for the per-viewer enrichment
2529
+ * model). Names who may see this column's value in a cloud. Omitted ⇒
2530
+ * `row-audience` — the value is visible to exactly whoever can see the row,
2531
+ * which is today's behavior, so leaving it unset changes nothing. Later
2532
+ * stages parse a richer grammar (e.g. `subject+role:hr`) and generate a
2533
+ * cell-masking view from it; Stage-0 only records the metadata.
2534
+ */
2535
+ audience?: string;
2289
2536
  }
2290
2537
  /**
2291
2538
  * Inline render spec inside YAML — a flat object alternative to `TemplateRenderSpec`.
@@ -3062,6 +3309,80 @@ declare function hashFile(srcPath: string): Promise<string>;
3062
3309
  */
3063
3310
  declare function attachBlob(srcPath: string, latticeRoot: string): Promise<BlobMetadata>;
3064
3311
 
3312
+ /**
3313
+ * S3 object storage for file blobs (cloud workspaces). Bytes uploaded to a cloud
3314
+ * are written to S3 under a content-addressed key so every member who can see the
3315
+ * `files` row can pull them down — see `docs/cloud.md`. `@aws-sdk/client-s3` is an
3316
+ * OPTIONAL dependency, lazy-imported the same way `sharp` is, so a build without it
3317
+ * still loads: callers catch {@link S3UnavailableError} and fall back to local-only
3318
+ * blobs rather than failing.
3319
+ *
3320
+ * Access control is NOT enforced here — it rides entirely on the `files`-row
3321
+ * Postgres RLS at the serve route (`files-routes.ts`): a member only ever learns a
3322
+ * key for a row they can SELECT, keys are unguessable (sha256), and the bucket
3323
+ * credential is least-privilege (GetObject/PutObject only, no ListBucket). See the
3324
+ * security notes in `docs/cloud.md`.
3325
+ */
3326
+ /** A minimal remote blob store — the only surface the upload + serve paths use. */
3327
+ interface RemoteBlobStore {
3328
+ /** Idempotent upload under `key` (same content ⇒ same key ⇒ same object). */
3329
+ put(key: string, body: Buffer, opts?: {
3330
+ contentType?: string;
3331
+ }): Promise<void>;
3332
+ /** Stream the object's bytes back (to pipe into an HTTP response). */
3333
+ get(key: string): Promise<NodeJS.ReadableStream>;
3334
+ /** Whether an object exists under `key`. */
3335
+ exists(key: string): Promise<boolean>;
3336
+ }
3337
+ /** Connection config for an S3 (or S3-compatible) bucket. Credentials are optional
3338
+ * — when omitted, the AWS default credential chain (env / shared config / IAM
3339
+ * role) is used. `endpoint` (+ path-style) targets R2 / MinIO / LocalStack. */
3340
+ interface S3StoreConfig {
3341
+ bucket: string;
3342
+ region: string;
3343
+ prefix: string;
3344
+ endpoint?: string;
3345
+ credentials?: {
3346
+ accessKeyId: string;
3347
+ secretAccessKey: string;
3348
+ };
3349
+ }
3350
+ /** Thrown when `@aws-sdk/client-s3` is not installed. Callers degrade to
3351
+ * local-only behavior; they do not 500. */
3352
+ declare class S3UnavailableError extends Error {
3353
+ constructor(message: string);
3354
+ }
3355
+ /** Content-addressed object key: `<prefix>/<sha256>` (POSIX separators, stable
3356
+ * across machines + idempotent). A leading/trailing slash on the prefix is
3357
+ * normalized away. */
3358
+ declare function s3Key(prefix: string, sha256: string): string;
3359
+ /**
3360
+ * Build a {@link RemoteBlobStore} backed by S3. Throws {@link S3UnavailableError}
3361
+ * if the optional `@aws-sdk/client-s3` dependency is absent — the lazy import
3362
+ * mirrors the `sharp` pattern so the module loads without it.
3363
+ */
3364
+ declare function createS3Store(cfg: S3StoreConfig): Promise<RemoteBlobStore>;
3365
+
3366
+ /**
3367
+ * The S3 config for a cloud workspace, resolved per-member. `enabled` gates the
3368
+ * whole feature; the rest is the {@link S3StoreConfig} the store consumes. Stored
3369
+ * machine-local + encrypted (see user-config `s3-config.enc`), or supplied via env
3370
+ * for headless / CI.
3371
+ */
3372
+ interface S3Config extends S3StoreConfig {
3373
+ enabled: boolean;
3374
+ }
3375
+ /** Extract the cloud workspace LABEL from a config's `db:` line
3376
+ * (`db: ${LATTICE_DB:label}`), so the S3 config can be keyed by it. Null when the
3377
+ * config isn't a labelled cloud connection. */
3378
+ declare function activeWorkspaceLabel(configPath: string): string | null;
3379
+ /**
3380
+ * Resolve the active workspace's S3 config: the per-member machine-local config
3381
+ * keyed by the workspace label, else the environment. Returns null (S3 off) when
3382
+ * neither is configured — so a non-cloud / S3-disabled workspace is untouched.
3383
+ */
3384
+ declare function resolveActiveS3Config(configPath: string | undefined): S3Config | null;
3385
+
3065
3386
  /**
3066
3387
  * Machine-local lattice user config — small files that live outside any
3067
3388
  * single Lattice DB so a user's identity, encryption master key, saved
@@ -3416,7 +3737,7 @@ declare function importLegacyUserConfig(root: string): MigrateResult;
3416
3737
  * local and cloud sources share one set of utilities.
3417
3738
  */
3418
3739
  type RefKind = 'blob' | 'local_ref' | 'cloud_ref';
3419
- type RefProvider = 'fs' | 'web' | 'gdrive';
3740
+ type RefProvider = 'fs' | 'web' | 'gdrive' | 's3';
3420
3741
  /** The subset of a `files` row the resolver reads. */
3421
3742
  interface FilesRow {
3422
3743
  id?: string;
@@ -3578,520 +3899,6 @@ declare function referenceUrl(url: string, opts?: {
3578
3899
  allowPrivate?: boolean;
3579
3900
  }): Promise<ReferenceMetadata>;
3580
3901
 
3581
- type ColumnType = 'TEXT' | 'INTEGER' | 'REAL' | 'BLOB' | 'JSONB';
3582
- interface ColumnSpec {
3583
- type: ColumnType;
3584
- notNull?: boolean;
3585
- pk?: true;
3586
- default?: string;
3587
- }
3588
- interface SchemaSpec {
3589
- columns: Record<string, ColumnSpec>;
3590
- primaryKey: string | string[];
3591
- tableConstraints?: string[];
3592
- relations?: Record<string, {
3593
- kind: 'belongsTo';
3594
- table: string;
3595
- foreignKey: string;
3596
- }>;
3597
- schemaVersion: number;
3598
- }
3599
-
3600
- /**
3601
- * Cloud-connect probe — non-destructive inspection of a candidate
3602
- * target Lattice (typically a BYO Postgres URL the user wants to
3603
- * connect to). Used by the GUI's "Migrate to cloud" and "Connect to
3604
- * existing cloud" wizards to surface team-membership requirements
3605
- * before the user commits, but exported as a public API so library
3606
- * consumers can pre-flight the same check.
3607
- *
3608
- * The function opens a short-lived Lattice against the URL, reads
3609
- * the singleton `__lattice_team_identity` row (if any), then closes.
3610
- * It does NOT mutate the target — `init()` does run, which applies
3611
- * the base schema, but no rows are inserted and no GUI/native
3612
- * registration happens here.
3613
- */
3614
- interface CloudProbeResult {
3615
- /** True iff a Lattice could be opened + init()'d successfully against the URL. */
3616
- reachable: boolean;
3617
- /** Adapter dialect of the probed URL. Useful for the GUI to label cards. */
3618
- dialect: 'sqlite' | 'postgres';
3619
- /** True iff the probed Lattice has a populated `__lattice_team_identity` row. */
3620
- teamEnabled: boolean;
3621
- /** Team name from `__lattice_team_identity.team_name`, if `teamEnabled`. */
3622
- teamName?: string;
3623
- /** Underlying error message when `reachable: false`. */
3624
- error?: string;
3625
- }
3626
- /**
3627
- * Probe a candidate Lattice URL for reachability + team status.
3628
- *
3629
- * Implementation note: this opens a real Lattice with no schema
3630
- * registered beyond what `init()` applies internally, then queries
3631
- * a single row. The `__lattice_team_identity` table doesn't exist on
3632
- * untouched DBs — the query falls through to a "table not found"
3633
- * which we treat as `teamEnabled: false`. On a Lattice that's been
3634
- * through `lattice gui` (which registers the team-identity table
3635
- * during openConfig), the table exists but may be empty, also
3636
- * `teamEnabled: false`.
3637
- *
3638
- * Never throws. Errors are returned in the result's `error` field
3639
- * with `reachable: false`.
3640
- */
3641
- declare function probeCloud(targetUrl: string): Promise<CloudProbeResult>;
3642
-
3643
- /**
3644
- * Local-side client for a Lattice Teams cloud. Wraps the cloud HTTP API
3645
- * and persists per-team connection metadata into the local lattice's
3646
- * `__lattice_team_connections` table.
3647
- *
3648
- * Phase 2 covers identity + team management: register, redeem-invite,
3649
- * list-teams, create-team, delete-team, list-members, invite, kick.
3650
- * Phases 3 and 4 add object sharing, row link/unlink, and the polling
3651
- * sync loop.
3652
- */
3653
- interface TeamSummary {
3654
- id: string;
3655
- name: string;
3656
- role: string;
3657
- }
3658
- interface RegisterResponse {
3659
- user: {
3660
- id: string;
3661
- email: string;
3662
- name: string;
3663
- };
3664
- raw_token: string;
3665
- team: TeamSummary;
3666
- }
3667
- interface RedeemResponse {
3668
- user: {
3669
- id: string;
3670
- email: string;
3671
- name: string;
3672
- };
3673
- raw_token: string;
3674
- team: {
3675
- id: string;
3676
- name: string;
3677
- };
3678
- }
3679
- interface MemberSummary {
3680
- user_id: string;
3681
- email: string | null;
3682
- name: string | null;
3683
- role: string;
3684
- joined_at: string;
3685
- }
3686
- /**
3687
- * An invitation that has been issued but not yet redeemed — surfaced in the
3688
- * member list so the owner can see who's been invited but hasn't joined.
3689
- */
3690
- interface PendingInvitationSummary {
3691
- id: string;
3692
- invitee_email: string;
3693
- invited_at: string;
3694
- expires_at: string | null;
3695
- expired: boolean;
3696
- }
3697
- interface InviteResponse {
3698
- id: string;
3699
- raw_token: string;
3700
- expires_at: string;
3701
- team_name: string;
3702
- invitee_email: string;
3703
- }
3704
- interface TeamConnection {
3705
- team_id: string;
3706
- team_name: string;
3707
- cloud_url: string;
3708
- my_user_id: string;
3709
- api_token: string;
3710
- joined_at: string;
3711
- }
3712
- interface SharedObjectSummary {
3713
- table: string;
3714
- schema_version: number;
3715
- schema_spec: SchemaSpec;
3716
- created_by_user_id: string;
3717
- created_at: string;
3718
- updated_at: string;
3719
- }
3720
- interface ShareObjectResponse {
3721
- table: string;
3722
- schema_version: number;
3723
- seq: number;
3724
- schema_spec: SchemaSpec;
3725
- }
3726
- interface ChangeEnvelope {
3727
- seq: number;
3728
- table_name: string | null;
3729
- pk: string | null;
3730
- /**
3731
- * Cloud-emitted ops are `schema`/`unshare`/`link`/`unlink`/`upsert`/`delete`.
3732
- * `divergence` is a client-side-only marker the puller writes into the DLQ
3733
- * when a non-owner local edit was overwritten by an owner update — it is
3734
- * never emitted by the cloud, and applying it is a no-op (the LWW overwrite
3735
- * already happened; the entry exists so the lost local content is visible).
3736
- */
3737
- op: 'schema' | 'unshare' | 'link' | 'unlink' | 'upsert' | 'delete' | 'divergence';
3738
- payload: unknown;
3739
- owner_user_id: string | null;
3740
- created_at: string;
3741
- }
3742
- /**
3743
- * A parsed dead-letter-queue entry — a change envelope that failed to apply
3744
- * during a pull (or a non-owner-overwrite divergence notice), surfaced so an
3745
- * operator can inspect and retry instead of it sitting invisible behind a
3746
- * count. See {@link TeamsClient.listDlq}.
3747
- */
3748
- interface DlqEntry {
3749
- id: string;
3750
- team_id: string;
3751
- /** Table the failed envelope targeted (null for schema-level ops). */
3752
- table_name: string | null;
3753
- /** Primary key the failed envelope targeted (null for schema-level ops). */
3754
- pk: string | null;
3755
- /** The envelope op, e.g. `upsert` / `link` / `divergence`. */
3756
- op: string;
3757
- /** The error that caused the envelope to land in the DLQ. */
3758
- error: string;
3759
- created_at: string;
3760
- /** The full envelope as stored, for retry / inspection. */
3761
- envelope: ChangeEnvelope;
3762
- }
3763
- interface DlqRetryResult {
3764
- retried: number;
3765
- succeeded: number;
3766
- failed: number;
3767
- }
3768
- interface SyncStatus {
3769
- team_id: string;
3770
- team_name: string;
3771
- last_change_seq: number | null;
3772
- outbox_depth: number;
3773
- outbox_failing: number;
3774
- dlq_depth: number;
3775
- local_links: number;
3776
- }
3777
- interface PullResult {
3778
- applied: number;
3779
- last_seq: number;
3780
- dlq_count: number;
3781
- }
3782
- interface PushResult {
3783
- pushed: number;
3784
- failed: number;
3785
- }
3786
- interface SyncSharedSchemasResult {
3787
- applied: {
3788
- table: string;
3789
- schema_version: number;
3790
- }[];
3791
- conflicts: {
3792
- table: string;
3793
- reason: string;
3794
- }[];
3795
- }
3796
- declare class TeamsClient {
3797
- private readonly local;
3798
- private _tablesReady;
3799
- /**
3800
- * Set during a pull's envelope application so the local write-hook
3801
- * skips outbox insertion — otherwise B's pull of A's change would
3802
- * push it right back to the cloud.
3803
- */
3804
- private _isReplaying;
3805
- /** Tables for which a sync write-hook has already been registered. */
3806
- private readonly _hookedTables;
3807
- constructor(local: Lattice);
3808
- /**
3809
- * Lazy-register the local `__lattice_team_connections` table on first
3810
- * use. `defineLate` is idempotent, so calling this on every session
3811
- * start is safe.
3812
- */
3813
- ensureLocalTables(): Promise<void>;
3814
- /**
3815
- * Bootstrap-register on a fresh cloud and create the team in one
3816
- * atomic call. The cloud rejects this once any user exists (subsequent
3817
- * members join via `redeemInvite`). Returns the new user + bearer
3818
- * token + team summary so the caller can immediately save a
3819
- * connection.
3820
- *
3821
- * @param teamName The workspace display name (stored as `team_name` for
3822
- * backward compatibility — a cloud IS a workspace with members).
3823
- */
3824
- register(cloudUrl: string, email: string, name: string, teamName: string): Promise<RegisterResponse>;
3825
- redeemInvite(cloudUrl: string, inviteToken: string, email: string, name: string): Promise<RedeemResponse>;
3826
- /**
3827
- * Connect a local project to an existing cloud DB by URL. Probes
3828
- * the target for team status first; if it's a teams DB, the caller
3829
- * must pass `invite_token` + identity (email/name) and the method
3830
- * will redeem the invite and save the resulting bearer to
3831
- * `~/.lattice/keys/<label>.token`. The saved credential lands in
3832
- * `~/.lattice/db-credentials.enc` keyed by `label`. Caller is
3833
- * responsible for rewriting the YAML `db:` line to
3834
- * `${LATTICE_DB:<label>}` and reopening the active Lattice.
3835
- *
3836
- * Returns the probe result + (if redeemed) the member info. Throws
3837
- * if the target is unreachable, or if it's a teams DB and the
3838
- * caller omitted `invite_token`.
3839
- */
3840
- connectToExistingCloud(opts: {
3841
- label: string;
3842
- cloudUrl: string;
3843
- invite_token?: string;
3844
- email?: string;
3845
- name?: string;
3846
- }): Promise<{
3847
- probe: CloudProbeResult;
3848
- joinedAsMember?: {
3849
- user_id: string;
3850
- team_id: string;
3851
- };
3852
- }>;
3853
- /**
3854
- * Initialize a fresh cloud DB's owner: register the first member (who
3855
- * becomes owner) so the cloud's members + per-table sharing surface
3856
- * exists. This is NOT a "convert a cloud into a team" step — a cloud
3857
- * workspace IS a workspace with members; this just bootstraps the owner
3858
- * the first time a cloud is opened. The hosted server path is the only
3859
- * supported one:
3860
- *
3861
- * - `http(s)://…` — POST to the cloud's `/api/auth/register` endpoint
3862
- * (a hosted `lattice serve` teams server is fronting the Postgres).
3863
- * - `postgres(ql)://…` — rejected: direct postgres:// owner bootstrap
3864
- * is deprecated. Row-level security is enforced by the hosted server,
3865
- * so it is the only supported connection method for new workspaces.
3866
- *
3867
- * On success writes the bearer token to `~/.lattice/keys/<label>.token`
3868
- * **and** persists the local `__lattice_team_connections` row so the
3869
- * GUI's team-management API calls can authenticate immediately
3870
- * afterward (members, invites, kick, destroy). v1.13.4 added the
3871
- * connection-row write — the older v1.13 implementation only wrote
3872
- * the token file, leaving GUI authenticated calls with no
3873
- * `cloud_url` + `my_user_id` + `api_token_encrypted` row to read.
3874
- */
3875
- registerCloudOwner(opts: {
3876
- label: string;
3877
- cloudUrl: string;
3878
- /** Workspace display name (stored as `team_name` for backward compatibility). */
3879
- teamName: string;
3880
- email: string;
3881
- displayName: string;
3882
- }): Promise<RegisterResponse>;
3883
- /**
3884
- * Idempotently initialize a cloud Postgres DB as a collaborative cloud
3885
- * workspace (members + sharing). 1.16.3 deprecated the user-facing "team"
3886
- * concept and the explicit "upgrade to team" step — every cloud workspace
3887
- * gets this machinery automatically at migrate / connect / open time, so the
3888
- * members + per-table sharing surface is always available on a cloud DB.
3889
- *
3890
- * No-op (returns created:false) when the cloud already carries an identity.
3891
- * On a fresh cloud the caller becomes the owner. Race-safe: a concurrent
3892
- * initializer that wins the singleton insert is treated as success.
3893
- */
3894
- ensureCloudWorkspaceIdentity(opts: {
3895
- label: string;
3896
- cloudUrl: string;
3897
- workspaceName: string;
3898
- email: string;
3899
- displayName?: string;
3900
- }): Promise<{
3901
- created: boolean;
3902
- }>;
3903
- /** Destroy the singleton team. Creator-only on the cloud side. */
3904
- destroyTeam(cloudUrl: string, token: string): Promise<void>;
3905
- listMembers(cloudUrl: string, token: string, teamId: string): Promise<MemberSummary[]>;
3906
- listPendingInvitations(cloudUrl: string, token: string, teamId: string): Promise<PendingInvitationSummary[]>;
3907
- invite(cloudUrl: string, token: string, teamId: string, inviteeEmail: string, expiresInHours?: number, inviterUserId?: string): Promise<InviteResponse>;
3908
- kickMember(cloudUrl: string, token: string, teamId: string, userId: string): Promise<void>;
3909
- me(cloudUrl: string, token: string): Promise<{
3910
- user: {
3911
- id: string;
3912
- email: string | null;
3913
- name: string | null;
3914
- };
3915
- }>;
3916
- /**
3917
- * Share a table with the team. Re-sharing the same table bumps its
3918
- * `schema_version` and replaces the stored spec — useful for evolving
3919
- * shared schemas additively.
3920
- */
3921
- shareObject(cloudUrl: string, token: string, teamId: string, table: string, schemaSpec: SchemaSpec, inviterUserId?: string): Promise<ShareObjectResponse>;
3922
- /**
3923
- * Stop sharing a table. Only the original sharer or the team creator
3924
- * can call this. The cloud soft-deletes the `__lattice_shared_objects`
3925
- * row and appends an `unshare` envelope to the change log.
3926
- */
3927
- unshareObject(cloudUrl: string, token: string, teamId: string, table: string): Promise<void>;
3928
- listSharedObjects(cloudUrl: string, token: string, teamId: string): Promise<SharedObjectSummary[]>;
3929
- /**
3930
- * Pull change envelopes since the given sequence number. Phase 3
3931
- * emits `schema` and `unshare` ops; Phase 4 adds row-level ops. The
3932
- * `has_more` flag tells callers to loop until drained before sleeping.
3933
- */
3934
- fetchChangeBatch(cloudUrl: string, token: string, teamId: string, since?: number, limit?: number): Promise<{
3935
- envelopes: ChangeEnvelope[];
3936
- has_more: boolean;
3937
- }>;
3938
- /**
3939
- * Fetch every shared object on the team's cloud and apply each spec
3940
- * to the local lattice. New tables go through `defineLate`; existing
3941
- * tables get additive ALTER TABLE for any cloud-only columns. PK
3942
- * mismatches are surfaced as `TeamsSchemaConflictError`s and recorded
3943
- * in the returned summary so callers can present them to the user.
3944
- *
3945
- * Phase 4 will wrap this in a polling loop; Phase 3 invokes it on
3946
- * demand from the CLI / GUI.
3947
- */
3948
- syncSharedSchemas(connection: TeamConnection): Promise<SyncSharedSchemasResult>;
3949
- /**
3950
- * Apply a single cloud SchemaSpec to the local lattice. Returns true
3951
- * if any change was made (new table, new column), false if local was
3952
- * already in sync. Throws `TeamsSchemaConflictError` on PK mismatch.
3953
- */
3954
- applyCloudSchemaLocally(table: string, spec: SchemaSpec): Promise<boolean>;
3955
- /**
3956
- * Persist a team connection in the local lattice. If a row already
3957
- * exists for the same `team_id`, it's overwritten — the caller has
3958
- * presumably just redeemed a fresh invitation or recreated the team.
3959
- *
3960
- * Token encryption: Phase 2 stores tokens in plaintext. Lattice's
3961
- * existing AES-256-GCM encryption layer can wrap this column when the
3962
- * caller configures `encryptionKey`, but the entity-context encryption
3963
- * API doesn't apply to raw `define()`/`defineLate()` tables yet. A
3964
- * follow-up will enable per-column encryption for these internal
3965
- * tables; until then operators should keep their lattice DB file safe.
3966
- */
3967
- saveConnection(conn: {
3968
- team_id: string;
3969
- team_name: string;
3970
- cloud_url: string;
3971
- my_user_id: string;
3972
- api_token: string;
3973
- }): Promise<void>;
3974
- deleteConnection(teamId: string): Promise<void>;
3975
- listConnections(): Promise<TeamConnection[]>;
3976
- /**
3977
- * Resolve a team name to a local connection row. Throws if more than
3978
- * one matches (e.g. user joined two teams with the same name on
3979
- * different clouds) — caller must disambiguate with team_id.
3980
- */
3981
- findConnectionByName(teamName: string): Promise<TeamConnection | null>;
3982
- /**
3983
- * Link a local row to a team. Reads the current local row, POSTs the
3984
- * snapshot to the cloud (which creates a `__lattice_row_links` row +
3985
- * mirrors the data into the team's shared table + emits link/upsert
3986
- * envelopes), and records the link on the local side.
3987
- *
3988
- * Also ensures the sync write-hook is attached for `table` so future
3989
- * local writes to this row drain through the outbox to the cloud.
3990
- */
3991
- linkRow(connection: TeamConnection, table: string, pk: string): Promise<{
3992
- owner_user_id: string;
3993
- seq: number;
3994
- }>;
3995
- /**
3996
- * Remove a row from the team. The cloud verifies the caller is the
3997
- * owner (or team creator) and emits an `unlink` envelope; the local
3998
- * link row is removed and the local data row is kept in place (Phase
3999
- * 4 v1 default — receivers' pullers handle their own removal).
4000
- */
4001
- unlinkRow(connection: TeamConnection, table: string, pk: string): Promise<void>;
4002
- /**
4003
- * Scan `__lattice_local_links` for tables that currently have at
4004
- * least one link and ensure a write-hook is registered for each.
4005
- * Called by CLI sync commands at session start to re-arm hooks after
4006
- * a process restart (write hooks are bound to the in-memory Lattice
4007
- * instance, not the underlying DB).
4008
- */
4009
- attachWriteHooks(): Promise<void>;
4010
- /**
4011
- * Register a sync write-hook for a single table. Idempotent: a second
4012
- * call for the same table is a no-op. Called automatically by
4013
- * `linkRow` and `applyCloudSchemaLocally`; manual callers can invoke
4014
- * directly for tables that exist before any link/share happens.
4015
- *
4016
- * The hook captures local writes to linked rows into
4017
- * `__lattice_team_outbox`. The replay guard (`_isReplaying`) skips
4018
- * captures during envelope application so pulled changes don't get
4019
- * pushed back to the cloud.
4020
- */
4021
- ensureWriteHook(table: string): void;
4022
- private captureWrite;
4023
- /**
4024
- * Drain the outbox for one team in FIFO order. Each entry POSTs to
4025
- * the cloud's row endpoint (or DELETEs for soft-delete ops). 2xx
4026
- * deletes the outbox row; failures increment `attempts` + set
4027
- * `next_attempt_at` to an exponential-backoff future timestamp,
4028
- * leaving the row for a later drain.
4029
- *
4030
- * Phase 4 v1: no separate dead-letter for outbox entries. Repeated
4031
- * 4xx responses just keep retrying with a growing backoff — operator
4032
- * surfaces them via `lattice teams status`.
4033
- */
4034
- drainOutbox(connection: TeamConnection): Promise<PushResult>;
4035
- /**
4036
- * Pull change envelopes from the cloud and apply them to the local
4037
- * lattice. Loops internally until the cloud reports no more pending
4038
- * envelopes, so a single call drains arbitrary backlog. Bookkeeping:
4039
- * the local connection's `last_change_seq` advances per successful
4040
- * envelope; the replay guard prevents pulled writes from being re-
4041
- * pushed back to the cloud via the outbox.
4042
- *
4043
- * Individual envelope failures land in `__lattice_team_dlq` so one
4044
- * bad row doesn't stall the stream.
4045
- */
4046
- pullChanges(connection: TeamConnection, batchSize?: number): Promise<PullResult>;
4047
- private applyEnvelope;
4048
- /**
4049
- * Apply an `upsert` envelope to a local row, guarding against silently
4050
- * clobbering a non-owner's local edit.
4051
- *
4052
- * A non-owner who edits a mirrored row locally produces no outbox entry
4053
- * (only owners push), so the next owner update would overwrite it with no
4054
- * trace. Before the last-write-wins overwrite, this compares the current
4055
- * local row against the hash captured at the last sync (`synced_hash` on
4056
- * the link row): if they differ, the local copy diverged, so we record a
4057
- * `divergence` entry in the DLQ — capturing the lost local content — then
4058
- * still apply (LWW keeps sync converging). The loss is now visible via
4059
- * `lattice teams dlq list` instead of being silent.
4060
- *
4061
- * Owner rows are never overwritten by foreign upserts, so they skip the
4062
- * check. `synced_hash` is (re)stamped from the stored row after every apply.
4063
- */
4064
- private applyUpsertEnvelope;
4065
- private findLocalLink;
4066
- /**
4067
- * List the DLQ entries for a team, newest first, with the stored envelope
4068
- * parsed for inspection.
4069
- */
4070
- listDlq(connection: TeamConnection): Promise<DlqEntry[]>;
4071
- /**
4072
- * Replay DLQ entries through {@link applyEnvelope}. With `id`, retries just
4073
- * that entry; otherwise retries every entry for the team (oldest first, so
4074
- * dependencies replay before dependents). An entry that applies cleanly is
4075
- * deleted; one that fails again stays, with its `error` refreshed. Runs
4076
- * under the replay guard so a re-applied row isn't pushed back to the cloud.
4077
- */
4078
- retryDlq(connection: TeamConnection, id?: string): Promise<DlqRetryResult>;
4079
- /**
4080
- * Delete DLQ entries without applying them. With `id`, purges just that
4081
- * entry; otherwise purges every entry for the team. Returns the count
4082
- * removed. Use to discard divergence notices or envelopes that will never
4083
- * apply.
4084
- */
4085
- purgeDlq(connection: TeamConnection, id?: string): Promise<number>;
4086
- getStatus(connection: TeamConnection): Promise<SyncStatus>;
4087
- private fetchUnauthed;
4088
- private fetchAuthed;
4089
- }
4090
- declare class TeamsHttpError extends Error {
4091
- readonly status: number;
4092
- constructor(status: number, message: string);
4093
- }
4094
-
4095
3902
  /**
4096
3903
  * Cloud migration — copy a Lattice's data from one backing store to
4097
3904
  * another. Used by the GUI's "Migrate to cloud" flow, but exported as
@@ -4172,58 +3979,420 @@ declare function openTargetLatticeForMigration(configPath: string, targetUrl: st
4172
3979
  declare function archiveLocalSqlite(dbPath: string): string;
4173
3980
 
4174
3981
  /**
4175
- * Shape returned by both the HTTP register path and the Postgres-direct
4176
- * register path. Kept aligned with `RegisterResponse` in
4177
- * `src/teams/client.ts`.
3982
+ * Cloud-connect probe non-destructive inspection of a candidate target
3983
+ * Lattice (a Postgres URL the user wants to migrate to or join). Used by the
3984
+ * GUI's "Migrate to cloud" and "Join a cloud" flows to tell, before the user
3985
+ * commits, whether a URL points at a fresh database or an already-secured
3986
+ * Lattice cloud. Exported as public API so library consumers can pre-flight the
3987
+ * same check.
3988
+ *
3989
+ * A "cloud" in v3 is a shared Postgres database with Lattice RLS installed —
3990
+ * its tell is the bookkeeping table `__lattice_owners` (created by
3991
+ * `installCloudRls`). The probe opens the URL with `introspectOnly` (NO DDL, so
3992
+ * it works even when the caller holds a scoped, non-superuser member role), asks
3993
+ * Postgres whether that table exists, and closes. It never mutates the target.
4178
3994
  */
4179
- interface DirectRegisterResult {
4180
- user: {
4181
- id: string;
4182
- email: string;
4183
- name: string;
4184
- };
4185
- raw_token: string;
4186
- team: {
4187
- id: string;
4188
- name: string;
4189
- role: 'creator';
4190
- };
3995
+ interface CloudProbeResult {
3996
+ /** True iff the URL could be opened (connected + authenticated). */
3997
+ reachable: boolean;
3998
+ /** Adapter dialect of the probed URL. */
3999
+ dialect: 'sqlite' | 'postgres';
4000
+ /**
4001
+ * True iff the target is an established Lattice cloud — Postgres with RLS
4002
+ * bookkeeping (`__lattice_owners`) present. A fresh Postgres (no Lattice
4003
+ * schema yet) and any SQLite file are `false`: the former is a migration
4004
+ * target, the latter is a private local store.
4005
+ */
4006
+ isCloud: boolean;
4007
+ /** Underlying error message when `reachable: false`. */
4008
+ error?: string;
4191
4009
  }
4010
+ /** Detect the RLS bookkeeping table without assuming SELECT privilege on it.
4011
+ * `to_regclass` returns the table's OID name when it exists and NULL when it
4012
+ * doesn't — and, unlike `SELECT FROM __lattice_owners`, it does not require any
4013
+ * privilege on the table, so a scoped member (who is denied SELECT on the
4014
+ * bookkeeping tables) still gets a truthful answer. */
4015
+ declare function cloudRlsInstalled(probe: Lattice): Promise<boolean>;
4192
4016
  /**
4193
- * True iff `url` parses as a `postgres://` / `postgresql://` URL used
4194
- * by the GUI's upgrade flow to decide between HTTP `register` and the
4195
- * direct-Postgres path implemented here.
4017
+ * Whether the connected role may create other roles the capability that
4018
+ * separates a cloud OWNER (ran the migration, owns the rows, can invite members)
4019
+ * from a scoped MEMBER (provisioned `NOCREATEROLE`). Read from
4020
+ * `pg_roles.rolcreaterole` for the live role. SQLite or any error → false.
4021
+ */
4022
+ declare function canManageRoles(db: Lattice): Promise<boolean>;
4023
+ /**
4024
+ * Probe a candidate Lattice URL for reachability + cloud status.
4025
+ *
4026
+ * Never throws. Errors are returned in the result's `error` field with
4027
+ * `reachable: false`.
4028
+ */
4029
+ declare function probeCloud(targetUrl: string): Promise<CloudProbeResult>;
4030
+
4031
+ /**
4032
+ * True iff `url` parses as a `postgres://` / `postgresql://` URL. Used by
4033
+ * the GUI to distinguish a cloud (shared Postgres) connection from a local
4034
+ * SQLite file path.
4196
4035
  */
4197
4036
  declare function isPostgresUrl(url: string): boolean;
4037
+
4038
+ /**
4039
+ * One-time bootstrap for a cloud: the ownership bookkeeping tables and the shared
4040
+ * `SECURITY DEFINER` helpers. Idempotent (`CREATE TABLE IF NOT EXISTS`,
4041
+ * `CREATE OR REPLACE FUNCTION`). Multi-statement — Postgres-only, so it never hits
4042
+ * the single-statement SQLite migration path.
4043
+ *
4044
+ * Every `SECURITY DEFINER` helper below gets `search_path` pinned at install time
4045
+ * via {@link pinDefinerSearchPath} (see its doc for the threat it closes). The pin
4046
+ * is applied in {@link installCloudRls}, not baked into the literal here, because
4047
+ * the cloud's schema name is only known at runtime (`current_schema()`).
4048
+ */
4049
+ /**
4050
+ * Group role every cloud member inherits. Table privileges are granted to the
4051
+ * group, so adding a shared table or a member is a single GRANT — while RLS still
4052
+ * filters rows per individual login role (`session_user`). The group grants
4053
+ * *access*, never *visibility*.
4054
+ */
4055
+ declare const MEMBER_GROUP = "lattice_members";
4056
+ /** Install the cloud RLS bootstrap (bookkeeping + helper functions). No-op on SQLite. */
4057
+ declare function installCloudRls(db: Lattice): Promise<void>;
4058
+ /**
4059
+ * Secure the observation substrate (`__lattice_changelog`) so a member reads
4060
+ * only what they're allowed to: a DERIVED observation only when it can reach
4061
+ * EVERY source it was derived from (so a hidden enrichment never reaches the
4062
+ * member — existence-hiding is structural), and a ground-truth / audit entry
4063
+ * only when the member OWNS the row it records. Both predicates route through the
4064
+ * `session_user`-keyed SECURITY DEFINER helpers, so they bind to the real member.
4065
+ * `FORCE ROW LEVEL SECURITY` applies the policy even to the table owner. No-op on
4066
+ * SQLite (single-user; no cross-viewer leak to guard). Run after the change-log
4067
+ * table exists (`Lattice.ensureObservationSubstrate`).
4068
+ *
4069
+ * Ground-truth entries are OWNER-ONLY (v2), not merely "row is visible". A
4070
+ * changelog row carries the full `changes`/`previous` JSON of the underlying row —
4071
+ * EVERY column in cleartext, including ones the `<table>_v` mask hides from a
4072
+ * non-owner (an `owner`-audience secret column, a role-gated column). If a member
4073
+ * who was merely granted the row could read its history, those masked columns
4074
+ * would leak in cleartext, bypassing column masking. The row's full mutation
4075
+ * history is an owner/audit artifact; a non-owner sees the row only through the
4076
+ * masked view, never its raw history. (The derived-observation branch is the
4077
+ * per-viewer enrichment path and is unaffected — it carries enrichment, not the
4078
+ * base row's masked columns.)
4079
+ */
4080
+ declare function enableChangelogRls(db: Lattice): Promise<void>;
4081
+ /**
4082
+ * Enable RLS on one shared table. No-op on SQLite. Idempotent via a per-table
4083
+ * version key. v3 bumps the key so existing clouds re-install the policy-aware
4084
+ * insert trigger (which now stamps the per-table `default_row_visibility` / forces
4085
+ * private under `never_share`) and pick up the `search_path` pin on the trigger
4086
+ * function — neither of which a v2-stamped clone would otherwise get.
4087
+ */
4088
+ declare function enableRlsForTable(db: Lattice, table: string, pkCols: readonly string[]): Promise<void>;
4089
+ /**
4090
+ * Stamp the current role as owner of every row that already exists in a table —
4091
+ * for data migrated into a cloud BEFORE the ownership trigger existed (the
4092
+ * trigger only fires on new writes). Without this, migrated rows have no
4093
+ * ownership record and RLS would hide them from everyone. Idempotent; no-op on
4094
+ * SQLite or an unkeyable table.
4095
+ */
4096
+ declare function backfillOwnership(db: Lattice, table: string, pkCols: readonly string[]): Promise<void>;
4097
+
4098
+ /** A URL-safe random password (48 hex chars) for a new member role. */
4099
+ declare function generateMemberPassword(): string;
4100
+ /**
4101
+ * Derive a safe, unique Postgres role name from a free-form label (e.g. an email
4102
+ * or display name). Lowercased, non-word chars collapsed to `_`, prefixed so it
4103
+ * always starts legally and namespaced under `lm_`, with a short random suffix so
4104
+ * two people with similar labels never collide.
4105
+ */
4106
+ declare function memberRoleName(label: string): string;
4107
+ /**
4108
+ * Create (or re-key) a scoped member LOGIN role and add it to the member group.
4109
+ * Idempotent on the role's existence: a re-invite rotates the password. Requires
4110
+ * the connection to hold `CREATEROLE`. After this, the member connects with
4111
+ * `postgres://<role>:<password>@<host>/<db>` and sees only its permitted rows.
4112
+ */
4113
+ declare function provisionMemberRole(db: Lattice, role: string, password: string): Promise<void>;
4114
+ /**
4115
+ * Change a row's sharing through the owner-only `lattice_set_row_visibility`
4116
+ * SECURITY DEFINER function. Only the row's owner (Postgres raises for anyone
4117
+ * else, enforced inside the function) may call it. `pk` is the row's canonical
4118
+ * primary-key string — a single-column key is the bare value; a composite key is
4119
+ * its columns joined by TAB, matching Lattice's serialization.
4120
+ */
4121
+ declare function setRowVisibility(db: Lattice, table: string, pk: string, visibility: string): Promise<void>;
4122
+ /**
4123
+ * Per-card audience override: grant (or revoke) one member access to ONE masked
4124
+ * cell — a specific (table, pk, column) — without changing the column's
4125
+ * schema-level audience. Owner-only (the SQL function raises for a non-owner).
4126
+ * `pk` is the row's canonical primary-key string.
4127
+ */
4128
+ declare function grantCell(db: Lattice, table: string, pk: string, column: string, grantee: string): Promise<void>;
4129
+ declare function revokeCell(db: Lattice, table: string, pk: string, column: string, grantee: string): Promise<void>;
4130
+ /**
4131
+ * Remove a member: clear its privileges and drop the role. NOTE: rows the member
4132
+ * owned remain in their tables but become unreachable (their `owner_role` no
4133
+ * longer matches any login role, and RLS shows a row only to its owner / grantees
4134
+ * / everyone) — reassigning or purging a departed member's rows is a separate,
4135
+ * deliberate step, not a side effect of revoking access.
4136
+ */
4137
+ declare function revokeMemberRole(db: Lattice, role: string): Promise<void>;
4138
+
4139
+ /**
4140
+ * Physical-schema discovery for cloud members. A member connects to a shared
4141
+ * cloud as a scoped role whose local config may declare NO entities — yet the
4142
+ * GUI must show every table the member is allowed to use. Postgres only exposes
4143
+ * a table in `pg_tables` / `information_schema` to a role that holds a privilege
4144
+ * on it, so listing the role's visible user tables is exactly the set RLS lets
4145
+ * it touch: the member's granted tables, never another's bookkeeping.
4146
+ */
4147
+ interface DiscoveredTable {
4148
+ name: string;
4149
+ columns: string[];
4150
+ /** Primary-key column(s), in key order. May be empty for a keyless table. */
4151
+ pk: string[];
4152
+ }
4153
+ /**
4154
+ * List the user tables a member's role is actually privileged to use, excluding
4155
+ * Lattice/GUI bookkeeping (anything starting `_`). `information_schema.tables`
4156
+ * is privilege-filtered — it shows a role only the tables it holds a grant on or
4157
+ * owns — so this returns exactly the member's reachable set, never another
4158
+ * member's bookkeeping (which is granted to no one). Scoped to `current_schema()`
4159
+ * so it follows the connection's search_path (the cloud's `public` in production).
4160
+ * Returns each table's columns + primary key so the caller can register it.
4161
+ * Postgres-only — returns `[]` on SQLite (a private, single-user store).
4162
+ */
4163
+ declare function discoverCloudTables(db: Lattice): Promise<DiscoveredTable[]>;
4164
+
4165
+ /** Row context the `owner` clause needs (the table literal + pk SQL expression). */
4166
+ interface AudienceRowCtx {
4167
+ tableLit: string;
4168
+ pkExpr: string;
4169
+ }
4170
+ /** True when this audience means "no mask" (visible to whoever can see the row). */
4171
+ declare function isRowAudience(audience: string | undefined): boolean;
4172
+ /**
4173
+ * Compile a column `audience` spec into a boolean SQL predicate over the helper
4174
+ * functions. Returns `'true'` for the row-audience / everyone case. Throws on an
4175
+ * unknown or malformed clause.
4176
+ */
4177
+ declare function audiencePredicate(audience: string, ctx?: AudienceRowCtx): string;
4178
+ /** Whether a table needs a masking view at all (any column has a real audience). */
4179
+ declare function tableNeedsAudienceView(columnAudience: Record<string, string>): boolean;
4180
+ /**
4181
+ * SQL to (re)generate a table's cell-masking view, point members at it, and make
4182
+ * the base table's columns unreachable to members so the mask can't be bypassed:
4183
+ *
4184
+ * - `CREATE OR REPLACE VIEW <t>_v` — every column passes through, except
4185
+ * audience columns which become `CASE WHEN <predicate> THEN col END`.
4186
+ * - The view re-applies ROW visibility with `WHERE lattice_row_visible(t, pk)`.
4187
+ * This is essential: the view runs with its OWNER's rights, so the base
4188
+ * table's RLS would be evaluated as the owner (who sees everything). The
4189
+ * `session_user`-keyed SECURITY DEFINER helper re-binds row filtering to the
4190
+ * real member, so an owner-defined view still filters per viewer.
4191
+ * - `GRANT SELECT` on the view + `REVOKE SELECT` on the base from members: a
4192
+ * member reads only the masked, row-filtered view and cannot reach the raw
4193
+ * column. (Member writes to such a table flow through the observation path —
4194
+ * members keep INSERT/UPDATE/DELETE on the base under RLS; only SELECT moves
4195
+ * to the view.)
4196
+ *
4197
+ * Idempotent. `columns` is the table's full column list (stable order); `pkCols`
4198
+ * its primary key, so the row filter matches the RLS policy's pk serialization.
4199
+ */
4200
+ declare function audienceViewSql(table: string, columns: readonly string[], pkCols: readonly string[], columnAudience: Record<string, string>): string;
4201
+ /**
4202
+ * Generate + install a table's cell-masking view (Postgres only; no-op on SQLite
4203
+ * and on a table with no audience columns). Versioned by a content hash of the
4204
+ * columns / pk / column-audience so a changed spec regenerates and an unchanged
4205
+ * one is skipped. Run AFTER the table + RLS exist (the view reuses the row
4206
+ * visibility helper and revokes the base SELECT that enableRlsForTable granted).
4207
+ */
4208
+ declare function enableAudienceView(db: Lattice, table: string, columns: readonly string[], pkCols: readonly string[], columnAudience: Record<string, string>): Promise<void>;
4209
+ /** Read a table's canonical column->audience map from __lattice_column_policy. */
4210
+ declare function loadColumnPolicy(db: Lattice, table: string): Promise<Record<string, string>>;
4211
+ /** Seed a table's YAML-declared audiences into __lattice_column_policy — ONE TIME
4212
+ * per table, the migration from the legacy on-disk spec to the DB-canonical store.
4213
+ * A marker in __lattice_migrations gates it: after the first run we never seed from
4214
+ * YAML again, because a later secureCloud would otherwise re-insert a policy row
4215
+ * for a column the owner has since CLEARED through the DB (a cleared column has no
4216
+ * row, so ON CONFLICT DO NOTHING would NOT protect it) — silently re-masking a
4217
+ * column the owner deliberately un-masked. Once seeded, the DB is canonical and
4218
+ * the only path to change a column's audience is setColumnAudience. */
4219
+ declare function seedColumnPolicyFromYaml(db: Lattice, table: string, yamlAudience: Record<string, string>): Promise<void>;
4220
+ /** Regenerate a table's cell-masking view FROM the DB column-policy (not YAML). If
4221
+ * the table now has no audience columns, drop the view and restore base SELECT to
4222
+ * members; otherwise (re)create the masked view and revoke base SELECT. Runs the
4223
+ * DDL directly (not via db.migrate) so it always reflects the current spec. */
4224
+ declare function regenerateAudienceViewFromDb(db: Lattice, table: string, columns: readonly string[], pkCols: readonly string[]): Promise<void>;
4225
+ /** Owner-only: set (or clear, with an empty spec) a column's audience in the DB and
4226
+ * regenerate the table's mask view from the DB. The owner gate is enforced inside
4227
+ * lattice_set_column_audience (raises for a non-owner). */
4228
+ declare function setColumnAudience(db: Lattice, table: string, column: string, audience: string, columns: readonly string[], pkCols: readonly string[]): Promise<void>;
4229
+
4230
+ /**
4231
+ * Per-table cloud policy (owner-controlled, Postgres-stored + enforced):
4232
+ * - `defaultRowVisibility` — the visibility NEW rows in this table are stamped
4233
+ * with (the per-table insert trigger reads `__lattice_table_policy`); default
4234
+ * `private` ⇒ unchanged behavior.
4235
+ * - `neverShare` — a hard exclusion (Secrets/Messages-class): the share/grant
4236
+ * SECURITY DEFINER functions raise for the table and the trigger forces its rows
4237
+ * private. Set at the data-model level, so a direct `psql` connection obeys it.
4238
+ *
4239
+ * These are thin wrappers over the owner-gated SQL functions in the RLS bootstrap
4240
+ * (`lattice_set_table_default_visibility` / `lattice_set_table_never_share`), which
4241
+ * raise unless the caller can create roles. No-op / safe defaults on SQLite.
4242
+ */
4243
+ type RowVisibilityDefault = 'private' | 'everyone';
4244
+ interface TablePolicy {
4245
+ defaultRowVisibility: RowVisibilityDefault;
4246
+ neverShare: boolean;
4247
+ }
4248
+ /** Read a table's policy. Returns the safe default (private, shareable) on SQLite
4249
+ * or when no policy row exists. */
4250
+ declare function getTablePolicy(db: Lattice, table: string): Promise<TablePolicy>;
4251
+ /** Owner-only: set the visibility NEW rows in `table` are created with. Raises (via
4252
+ * the SQL function) for a non-owner or for `everyone` on a never-share table. */
4253
+ declare function setTableDefaultVisibility(db: Lattice, table: string, visibility: RowVisibilityDefault): Promise<void>;
4254
+ /** Owner-only: mark (or unmark) a table never-shareable. When on, the share/grant
4255
+ * functions refuse it and its new rows are forced private. */
4256
+ declare function setTableNeverShare(db: Lattice, table: string, on: boolean): Promise<void>;
4257
+
4198
4258
  /**
4199
- * Direct-Postgres equivalent of the cloud's `POST /api/auth/register`.
4259
+ * The per-viewer fold (the "local compile" of the per-viewer enrichment model).
4200
4260
  *
4201
- * The HTTP teams-cloud server (`lattice serve --team-cloud`) handles
4202
- * `register` by running an INSERT sequence inside its own request
4203
- * handler. This helper does the same sequence locally by opening the
4204
- * cloud Postgres directly useful when the GUI's "Migrate to cloud"
4205
- * or "Connect to existing cloud" flow has saved the **Postgres URL**
4206
- * as the cloud credential (no HTTP teams server in front).
4261
+ * Source-gated enrichment is per-viewer: a value derived from a file that one
4262
+ * member can't see must not appear for that member. Postgres RLS + the generated
4263
+ * mask view handle row visibility and fixed-policy columns; this handles the
4264
+ * remaining casea column whose VALUE differs by which sources you can reach.
4207
4265
  *
4208
- * Hard browser limitation that motivates this: when `cloudUrl` is a
4209
- * Postgres URL with embedded credentials, the HTTP path's `fetch(url)`
4210
- * throws "Request cannot be constructed from a URL that includes
4211
- * credentials" before any network IO happens. We have to drive the
4212
- * register flow against the database protocol, not HTTP.
4266
+ * The compile is a deterministic, programmatic fold (NOT AI): start from the
4267
+ * broadly-visible ground-truth projection, then replay the observations the
4268
+ * viewer is allowed to see latest audience-visible observation per attribute
4269
+ * wins. Because observations are additive and only the viewer-visible ones
4270
+ * contribute, the fold is provably leak-free (sound only for additive/monotonic
4271
+ * derivations) and revocation is structural: drop a viewer's access to a source
4272
+ * and every value derived from it silently reverts to the prior visible
4273
+ * observation (or ground truth), with no residue — no promotion, no copy left
4274
+ * behind. Run on the member's local replica over already-audience-gated
4275
+ * observations, so hidden observations never reach them (existence-hiding is
4276
+ * structural) and egress is paid once at pull, never per read.
4277
+ */
4278
+ /** One attribute-level observation — a single column's value with its provenance. */
4279
+ interface Observation {
4280
+ /** The column this observation sets. */
4281
+ attribute: string;
4282
+ /** The value it sets the column to. */
4283
+ value: unknown;
4284
+ /** Ordering key (ISO timestamp). Latest visible observation per attribute wins. */
4285
+ createdAt: string;
4286
+ /** `ground_truth` (always visible) or `derived` (gated by its source-set). */
4287
+ changeKind?: 'ground_truth' | 'derived' | null;
4288
+ /** Source ids that produced a derived value. The observation is visible to a
4289
+ * viewer only if the viewer can see EVERY one of them (intersection-of-sources
4290
+ * reader set — losing any source hides the derived value). */
4291
+ sourceRef?: readonly string[] | null;
4292
+ }
4293
+ /** What a given member can reach, for deciding observation visibility. */
4294
+ interface Viewer {
4295
+ /** Source ids (file primary keys) this member can currently see. */
4296
+ visibleSources: ReadonlySet<string>;
4297
+ }
4298
+ /**
4299
+ * Whether a viewer may see an observation. Ground-truth is always visible (it is
4300
+ * the broadly-shared projection). A derived observation is visible iff the viewer
4301
+ * can see every source it was derived from — so un-sharing or deleting any one
4302
+ * source drops it. An unsourced derived observation is treated as hidden (fail
4303
+ * closed): a derived value with no recorded provenance can't be proven safe.
4304
+ */
4305
+ declare function observationVisible(obs: Observation, viewer: Viewer): boolean;
4306
+ /**
4307
+ * Compile one per-viewer entity: overlay the viewer-visible observations onto the
4308
+ * ground-truth projection, latest-per-attribute winning. Pure + deterministic —
4309
+ * the same (ground, observations, viewer) always yields the same row, and an
4310
+ * observation the viewer can't see never affects the result.
4311
+ */
4312
+ declare function foldEntity(ground: Row, observations: readonly Observation[], viewer: Viewer): Row;
4313
+ /**
4314
+ * Expand a change-log row's `changes` object into per-attribute observations.
4315
+ * The change-log records one row per write with a JSON `changes` map; the fold
4316
+ * works per attribute, so each changed field becomes its own observation carrying
4317
+ * the write's provenance. (Caller supplies the already-parsed change-log entry.)
4318
+ */
4319
+ declare function observationsFromChange(entry: {
4320
+ changes: Record<string, unknown> | null;
4321
+ createdAt: string;
4322
+ changeKind?: 'ground_truth' | 'derived' | null;
4323
+ sourceRef?: readonly string[] | null;
4324
+ }): Observation[];
4325
+
4326
+ declare class FoldCache {
4327
+ private readonly cache;
4328
+ /** Compiled entity for (rowId, viewer), computing + caching it on a miss. */
4329
+ get(rowId: string, ground: Row, observations: readonly Observation[], viewer: Viewer): Row;
4330
+ /** Drop every cached version of a row — call when a new observation lands for
4331
+ * it (any viewer's compile may have changed). Cheap, exact, no over-eviction
4332
+ * of other rows. */
4333
+ invalidateRow(rowId: string): void;
4334
+ /** Drop everything (e.g. on a full replica re-pull). */
4335
+ clear(): void;
4336
+ /** Number of cached (row, viewer) compilations — for tests / introspection. */
4337
+ get size(): number;
4338
+ }
4339
+
4340
+ /**
4341
+ * Turn a Postgres database into a secured Lattice cloud, in place: install the
4342
+ * RLS bootstrap + the observation substrate, then for every registered user
4343
+ * table stamp the current role as owner of the existing rows and force RLS (plus
4344
+ * a cell-masking view for any audience columns). Idempotent and additive — safe
4345
+ * to run on a fresh migration target OR on an already-populated Postgres that
4346
+ * isn't a cloud yet (the "secure this cloud" cutover). No-op on SQLite.
4213
4347
  *
4214
- * Mirrors `handleRegister`'s invariants:
4215
- * - Refuses if any non-deleted user already exists on the cloud.
4216
- * - Refuses if the `__lattice_team_identity` singleton already exists.
4348
+ * Must run as a role that owns the tables and can create roles (a cloud
4349
+ * owner / DBA). `backfillOwnership` runs BEFORE `enableRlsForTable` so a
4350
+ * non-superuser owner can still SELECT every row to stamp it before FORCE RLS
4351
+ * filters the table to rows it already owns.
4352
+ */
4353
+ declare function secureCloud(db: Lattice): Promise<void>;
4354
+
4355
+ /**
4356
+ * Workspace-level settings for a cloud — cloud-wide values the OWNER controls and
4357
+ * members never see in the product surface. Stored in `__lattice_cloud_settings`,
4358
+ * a bookkeeping table members have no grant on — so its VALUE is unreadable to a
4359
+ * member (SELECT is denied, like every other `__lattice_*` table; the System view
4360
+ * may still list the table's existence + column names from the catalog, but never
4361
+ * its contents). It is reached only through two `SECURITY DEFINER` helpers:
4217
4362
  *
4218
- * On success returns the same shape the HTTP route returns so a
4219
- * direct-postgres register can mirror the hosted register path.
4363
+ * - `lattice_get_cloud_setting(key)` readable by members, because a member's
4364
+ * own chat must inject the value (the chat call is assembled in each member's
4365
+ * LOCAL gui process). This is the deliberate, documented ceiling: secrecy is
4366
+ * **app-mediated** (hidden from the UI + every API response), NOT cryptographic
4367
+ * — a member CAN read the value from their own session if they go looking.
4368
+ * - `lattice_set_cloud_setting(key, value)` — owner-only (RAISEs unless the
4369
+ * caller can create roles, the same gate as `lattice_assign_role`).
4220
4370
  *
4221
- * @deprecated Since 2.2 new direct registrations are rejected (a direct
4222
- * connection bypasses row-level security). Retained only for the
4223
- * grandfathered existing-connection path; will be removed in 3.0. Create or
4224
- * join new workspaces through a hosted Teams server (an `http(s)://` URL).
4371
+ * Postgres-only: a local SQLite workspace is single-user, so there is nothing to
4372
+ * keep secret and these are all no-ops / null there.
4373
+ */
4374
+ /** Setting key for the chat system prompt an owner bundles into every member's chat. */
4375
+ declare const CLOUD_SETTING_SYSTEM_PROMPT = "chat_system_prompt";
4376
+ /**
4377
+ * Install the workspace-settings table + helpers. Idempotent (`CREATE TABLE IF
4378
+ * NOT EXISTS` / `CREATE OR REPLACE FUNCTION`). No-op on SQLite. Run as the cloud
4379
+ * owner — `secureCloud` calls it for new clouds, and the owner-only settings
4380
+ * endpoint calls it lazily so an already-secured cloud picks it up on first use.
4381
+ */
4382
+ declare function installCloudSettings(db: Lattice): Promise<void>;
4383
+ /**
4384
+ * Read a cloud workspace setting via the SECURITY DEFINER getter. Best-effort:
4385
+ * returns null on SQLite, on a cloud that hasn't installed the helper yet (the
4386
+ * function is absent), or on any error — so a caller treats "unset" and "couldn't
4387
+ * read" identically (e.g. the chat path simply injects nothing).
4388
+ */
4389
+ declare function getCloudSetting(db: Lattice, key: string): Promise<string | null>;
4390
+ /**
4391
+ * Owner-only: write a cloud workspace setting via the SECURITY DEFINER setter.
4392
+ * The function RAISEs if the caller isn't a cloud owner; that surfaces here as a
4393
+ * thrown error which the (already owner-gated) endpoint reports. Not silent.
4225
4394
  */
4226
- declare function registerDirectViaPostgres(cloudUrl: string, email: string, name: string, teamName: string): Promise<DirectRegisterResult>;
4395
+ declare function setCloudSetting(db: Lattice, key: string, value: string): Promise<void>;
4227
4396
 
4228
4397
  /** A content block in the Anthropic message format used here. */
4229
4398
  type ContentBlock = {
@@ -4514,4 +4683,4 @@ interface PdfOptions {
4514
4683
  */
4515
4684
  declare function describePdf(auth: ClaudeAuth, path: string, opts?: PdfOptions): Promise<string>;
4516
4685
 
4517
- export { type AddWorkspaceOptions, type AdoptNativeOptions, type AdoptResult, type ApplyWriteResult, type AuditEvent, type AutoUpdateResult, type BelongsToRelation, type BelongsToSource, type BlobMetadata, type BuiltinTemplateName, CONFIG_SUBDIR, type CatalogEntity, type CatalogRecord, type ChangeEntry, type ChangelogOptions, type ClassifyMatch, type CleanupOptions, type CleanupResult, type CloudProbeResult, type CountOptions, type CrawlOptions, type CrawlResult, type CustomSource, DEFAULT_ENTRY_TYPES, DEFAULT_TYPE_ALIASES, type DirectRegisterResult, type EmbeddingsConfig, type EnrichOptions, type EnrichResult, type EnrichedSource, type EnrichmentLookup, type EntityContextDefinition, type EntityContextManifestEntry, type EntityFileManifestInfo, type EntityFileSource, type EntityFileSpec, type EntityProfileField, type EntityProfileSection, type EntityProfileTemplate, type EntityRenderSpec, type EntityRenderTemplate, type EntitySectionPerRow, type EntitySectionsTemplate, type EntityTableColumn, type EntityTableTemplate, type ExtractedObject, type FilesRow, type Filter, type FilterOp, type FtsConfig, type FtsGroup, type FtsHit, type FtsOptions, type FtsResult, type HasManyRelation, type HasManySource, InMemoryStateStore, type InitOptions, type InviteResponse, LOCAL_DB_RELPATH, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type LinkOptions, type LlmClient, type LlmMessage, type ManyToManySource, type MarkdownTableColumn, type MemberSummary, type MigrateResult, type Migration, type MigrationOptions, type MigrationProgress, type MigrationResult, type MultiTableDefinition, NATIVE_ENTITY_DEFS, NATIVE_ENTITY_NAMES, NATIVE_REGISTRY_TABLE, type OrderBySpec, type OrganizeOptions, type OrganizeResult, type OrganizedCreation, type OrganizedLink, type ParseError, type ParseResult, type ParsedConfig, type PdfOptions, type PdfSenderInput, type PkLookup, PostgresAdapter, type PostgresAdapterOptions, type PreparedStatement, type PrimaryKey, type QueryOptions, READ_ONLY_HEADER, ROOT_DIRNAME, type ReadOnlyHeaderOptions, type ReconcileOptions, type ReconcileResult, type RedeemResponse, type RefKind, type RefProvider, type ReferenceMetadata, ReferenceUnavailableError, type RegisterResponse, type Relation, type RenderHooks, type RenderResult, type RenderSpec, type ReportConfig, type ReportResult, type ReportSection, type ReportSectionResult, type ResolveOptions, type ReverseSeedDetection, type ReverseSeedResult, type ReverseSeedTableResult, type ReverseSyncError, type ReverseSyncResult, type ReverseSyncUpdate, type RewardScores, type Row, SQLiteAdapter, type SchemaEntity, type SearchOptions, type SearchResult, type SecurityOptions, type SeedConfig, type SeedLinkSpec, SeedReconciliationError, type SeedResult, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceHandle, type SourceMetadata, type SourceQueryOptions, type StopFn, type StorageAdapter, type SyncResult, type TableDefinition, type TeamConnection, type TeamSummary, TeamsClient, TeamsHttpError, type TemplateRenderSpec, type TurnParams, type TurnResult, type UnresolvedLink, type UpsertByNaturalKeyOptions, type UserIdentity, type UserPreferences, type VisionOptions, type VisionSenderInput, WORKSPACES_SUBDIR, type WatchOptions, type WorkspacePaths, type WorkspaceRecord, type WorkspaceRegistry, type WriteHook, type WriteHookContext, type WritebackDefinition, type WritebackStateStore, type WritebackValidationResult, addWorkspace, adoptNativeEntities, analyticsEnabled, applyTokenBudget, applyWriteEntry, archiveLocalSqlite, assertSafeUrl, attachBlob, autoFtsColumns, autoUpdate, classifyLinks, configDir, contentHash, crawlUrl, createReadOnlyHeader, createSQLiteStateStore, decrypt, defaultWorkspaceYaml, deleteDbCredential, deleteToken, deriveCanonicalContexts, deriveKey, describeImage, describePdf, encrypt, enrichKnowledge, ensureFtsIndex, ensureLatticeRoot, entityFileNames, estimateTokens, extractObjects, findLatticeRoot, fixSchemaConflicts, frontmatter, ftsTableName, fullTextSearch, generateEntryId, generateWriteEntryId, getActiveWorkspace, getDbCredential, getOrCreateMasterKey, getWorkspace, hasFtsIndex, hashFile, importLegacyUserConfig, isEncrypted, isNativeEntity, isPostgresUrl, isPrivateIp, isV1EntityFiles, listDbCredentials, listNativeBindings, listTokens, listWorkspaces, manifestPath, markdownTable, migrateLatticeData, normalizeEntityFiles, openTargetLatticeForMigration, organizeSource, parseConfigFile, parseConfigString, parseMarkdownEntries, parseMatches, parseObjects, parseSessionMD, parseSessionWrites, probeCloud, providerForUrl, readIdentity, readManifest, readPreferences, readRegistry, readToken, referenceLocalFile, referenceUrl, registerDirectViaPostgres, registerNativeEntities, registryPath, resolveLatticeRoot, resolveSource, resolveWorkspacePaths, rootConfigDir, saveDbCredential, saveDbCredentialForTeam, setActiveWorkspace, slugify, summarizeText, toSafeDirName, truncate, validateEntryId, workspaceBlobsDir, workspaceConfigPath, workspaceContextDir, workspaceDataDir, workspaceDbPath, workspaceDir, workspacesDir, writeIdentity, writeManifest, writePreferences, writeRegistry, writeToken };
4686
+ export { type AddWorkspaceOptions, type AdoptNativeOptions, type AdoptResult, type ApplyWriteResult, type AudienceRowCtx, type AuditEvent, type AutoUpdateResult, type BelongsToRelation, type BelongsToSource, type BlobMetadata, type BuiltinTemplateName, CLOUD_SETTING_SYSTEM_PROMPT, CONFIG_SUBDIR, type CatalogEntity, type CatalogRecord, type ChangeEntry, type ChangelogOptions, type ClassifyMatch, type CleanupOptions, type CleanupResult, type CloudProbeResult, type CountOptions, type CrawlOptions, type CrawlResult, type CustomSource, DEFAULT_ENTRY_TYPES, DEFAULT_TYPE_ALIASES, type DiscoveredTable, type EmbeddingsConfig, type EnrichOptions, type EnrichResult, type EnrichedSource, type EnrichmentLookup, type EntityContextDefinition, type EntityContextManifestEntry, type EntityFileManifestInfo, type EntityFileSource, type EntityFileSpec, type EntityProfileField, type EntityProfileSection, type EntityProfileTemplate, type EntityRenderSpec, type EntityRenderTemplate, type EntitySectionPerRow, type EntitySectionsTemplate, type EntityTableColumn, type EntityTableTemplate, type ExtractedObject, type FilesRow, type Filter, type FilterOp, FoldCache, type FtsConfig, type FtsGroup, type FtsHit, type FtsOptions, type FtsResult, type HasManyRelation, type HasManySource, InMemorySourceKeyStore, InMemoryStateStore, type InitOptions, LOCAL_DB_RELPATH, Lattice, type LatticeConfig, type LatticeConfigInput, type LatticeEntityDef, type LatticeEntityRenderSpec, type LatticeFieldDef, type LatticeFieldType, type LatticeManifest, type LatticeOptions, type LinkOptions, type LlmClient, type LlmMessage, MEMBER_GROUP, type ManyToManySource, type MarkdownTableColumn, type MigrateResult, type Migration, type MigrationOptions, type MigrationProgress, type MigrationResult, type MultiTableDefinition, NATIVE_ENTITY_DEFS, NATIVE_ENTITY_NAMES, NATIVE_REGISTRY_TABLE, type Observation, type OrderBySpec, type OrganizeOptions, type OrganizeResult, type OrganizedCreation, type OrganizedLink, type ParseError, type ParseResult, type ParsedConfig, type PdfOptions, type PdfSenderInput, type PkLookup, PostgresAdapter, type PostgresAdapterOptions, type PreparedStatement, type PrimaryKey, ProgressThrottle, type QueryOptions, READ_ONLY_HEADER, ROOT_DIRNAME, type ReadOnlyHeaderOptions, type ReconcileOptions, type ReconcileResult, type RefKind, type RefProvider, type ReferenceMetadata, ReferenceUnavailableError, type Relation, type RemoteBlobStore, type RenderHooks, type RenderOptions, type RenderProgress, type RenderProgressCallback, type RenderProgressKind, type RenderResult, type RenderSpec, type ReportConfig, type ReportResult, type ReportSection, type ReportSectionResult, type ResolveOptions, type ReverseSeedDetection, type ReverseSeedResult, type ReverseSeedTableResult, type ReverseSyncError, type ReverseSyncResult, type ReverseSyncUpdate, type RewardScores, type Row, type RowVisibilityDefault, type S3Config, type S3StoreConfig, S3UnavailableError, SQLiteAdapter, type SchemaEntity, type SearchOptions, type SearchResult, type SecurityOptions, type SeedConfig, type SeedLinkSpec, SeedReconciliationError, type SeedResult, type SelfSource, type SessionEntry, type SessionParseOptions, type SessionWriteEntry, type SessionWriteOp, type SessionWriteParseResult, type SourceHandle, type SourceKeyStore, type SourceMetadata, type SourceQueryOptions, SourceShreddedError, type StopFn, type StorageAdapter, type SyncResult, type TableDefinition, type TablePolicy, type TemplateRenderSpec, type TurnParams, type TurnResult, type UnresolvedLink, type UpsertByNaturalKeyOptions, type UserIdentity, type UserPreferences, type Viewer, type VisionOptions, type VisionSenderInput, WORKSPACES_SUBDIR, type WatchOptions, type WorkspacePaths, type WorkspaceRecord, type WorkspaceRegistry, type WriteHook, type WriteHookContext, type WritebackDefinition, type WritebackStateStore, type WritebackValidationResult, activeWorkspaceLabel, addWorkspace, adoptNativeEntities, analyticsEnabled, applyTokenBudget, applyWriteEntry, archiveLocalSqlite, assertSafeUrl, attachBlob, audiencePredicate, audienceViewSql, autoFtsColumns, autoUpdate, backfillOwnership, canManageRoles, classifyLinks, cloudRlsInstalled, configDir, contentHash, crawlUrl, createReadOnlyHeader, createS3Store, createSQLiteStateStore, decrypt, defaultWorkspaceYaml, deleteDbCredential, deleteToken, deriveCanonicalContexts, deriveKey, describeImage, describePdf, discoverCloudTables, enableAudienceView, enableChangelogRls, enableRlsForTable, encrypt, enrichKnowledge, ensureFtsIndex, ensureLatticeRoot, entityFileNames, estimateTokens, extractObjects, findLatticeRoot, fixSchemaConflicts, foldEntity, frontmatter, ftsTableName, fullTextSearch, generateEntryId, generateMemberPassword, generateWriteEntryId, getActiveWorkspace, getCloudSetting, getDbCredential, getOrCreateMasterKey, getTablePolicy, getWorkspace, grantCell, hasFtsIndex, hashFile, importLegacyUserConfig, installCloudRls, installCloudSettings, isEncrypted, isNativeEntity, isPostgresUrl, isPrivateIp, isRowAudience, isV1EntityFiles, listDbCredentials, listNativeBindings, listTokens, listWorkspaces, loadColumnPolicy, manifestPath, markdownTable, memberRoleName, migrateLatticeData, normalizeEntityFiles, observationVisible, observationsFromChange, openTargetLatticeForMigration, openUnderSource, organizeSource, parseConfigFile, parseConfigString, parseMarkdownEntries, parseMatches, parseObjects, parseSessionMD, parseSessionWrites, probeCloud, providerForUrl, provisionMemberRole, readIdentity, readManifest, readPreferences, readRegistry, readToken, referenceLocalFile, referenceUrl, regenerateAudienceViewFromDb, registerNativeEntities, registryPath, resolveActiveS3Config, resolveLatticeRoot, resolveSource, resolveWorkspacePaths, revokeCell, revokeMemberRole, rootConfigDir, s3Key, saveDbCredential, saveDbCredentialForTeam, sealUnderSource, secureCloud, seedColumnPolicyFromYaml, setActiveWorkspace, setCloudSetting, setColumnAudience, setRowVisibility, setTableDefaultVisibility, setTableNeverShare, shredSource, slugify, summarizeText, tableNeedsAudienceView, toSafeDirName, truncate, validateEntryId, workspaceBlobsDir, workspaceConfigPath, workspaceContextDir, workspaceDataDir, workspaceDbPath, workspaceDir, workspacesDir, writeIdentity, writeManifest, writePreferences, writeRegistry, writeToken };