latticesql 2.2.4 → 3.0.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 +55503 -20083
  3. package/dist/index.cjs +47109 -8510
  4. package/dist/index.d.cts +595 -633
  5. package/dist/index.d.ts +595 -633
  6. package/dist/index.js +46867 -8304
  7. package/package.json +9 -9
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,64 @@ 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
+
1627
1734
  /**
1628
1735
  * Initialise Lattice from a YAML config file instead of an explicit path.
1629
1736
  *
@@ -1721,8 +1828,8 @@ declare class Lattice {
1721
1828
  * Idempotent: a second call for an already-registered table is a no-op
1722
1829
  * (the underlying CREATE TABLE IF NOT EXISTS is already idempotent at
1723
1830
  * 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.
1831
+ * re-registration). Useful for callers that may bootstrap their
1832
+ * internal tables on every session start.
1726
1833
  *
1727
1834
  * Throws if called before `init()` (use `define()` instead).
1728
1835
  */
@@ -1793,6 +1900,13 @@ declare class Lattice {
1793
1900
  * SchemaManager default.
1794
1901
  */
1795
1902
  getPrimaryKey(table: string): string[];
1903
+ /**
1904
+ * Per-column audience for a table (per-viewer enrichment) — column name →
1905
+ * audience identifier. A column absent from the map has `row-audience`
1906
+ * (visible to whoever can see the row). Empty until a column declares
1907
+ * `audience:`. Drives the generated cell-masking view.
1908
+ */
1909
+ getColumnAudience(table: string): Record<string, string>;
1796
1910
  /**
1797
1911
  * Return the raw column declarations for a registered table, as
1798
1912
  * passed to `define()` / `defineLate()`. Returns null for tables
@@ -1879,7 +1993,7 @@ declare class Lattice {
1879
1993
  * names containing quotes, semicolons, whitespace, etc.
1880
1994
  */
1881
1995
  private _assertIdent;
1882
- insert(table: string, row: Row): Promise<string>;
1996
+ insert(table: string, row: Row, provenance?: ChangeProvenance): Promise<string>;
1883
1997
  /**
1884
1998
  * Insert a row and return the full inserted row (including auto-generated
1885
1999
  * fields and defaults). Equivalent to `insert()` followed by `get()`.
@@ -1889,7 +2003,7 @@ declare class Lattice {
1889
2003
  insertReturning(table: string, row: Row): Promise<Row>;
1890
2004
  upsert(table: string, row: Row): Promise<string>;
1891
2005
  upsertBy(table: string, col: string, val: unknown, row: Row): Promise<string>;
1892
- update(table: string, id: PkLookup, row: Partial<Row>): Promise<void>;
2006
+ update(table: string, id: PkLookup, row: Partial<Row>, provenance?: ChangeProvenance): Promise<void>;
1893
2007
  /**
1894
2008
  * Update a row and return the full updated row. Equivalent to `update()`
1895
2009
  * followed by `get()`.
@@ -1897,7 +2011,43 @@ declare class Lattice {
1897
2011
  * @since 0.17.0
1898
2012
  */
1899
2013
  updateReturning(table: string, id: PkLookup, row: Partial<Row>): Promise<Row>;
1900
- delete(table: string, id: PkLookup): Promise<void>;
2014
+ delete(table: string, id: PkLookup, provenance?: ChangeProvenance): Promise<void>;
2015
+ /**
2016
+ * Record a DERIVED observation about a row WITHOUT mutating the canonical row.
2017
+ * The canonical row stays broadly-visible ground truth; the observation carries
2018
+ * its provenance (the source-set it was derived from) and is folded into a
2019
+ * per-viewer entity at read time by {@link foldForViewer} — visible only to a
2020
+ * viewer who can reach every one of its sources. This is how an AI enrichment
2021
+ * lands a per-viewer value without leaking it into the shared row, and without
2022
+ * moving the row's `updated_at` (so a viewer who can't see the source can't even
2023
+ * detect that the enrichment exists). `changes` maps column → derived value.
2024
+ */
2025
+ /** Ensure the observation substrate (`__lattice_changelog`) exists. Cloud setup
2026
+ * calls this before `enableChangelogRls` so the table is present to secure
2027
+ * even if nothing has written an observation yet. Idempotent. */
2028
+ ensureObservationSubstrate(): Promise<void>;
2029
+ observe(table: string, id: PkLookup, changes: Record<string, unknown>, provenance?: ChangeProvenance, opts?: {
2030
+ keyStore?: SourceKeyStore;
2031
+ }): Promise<void>;
2032
+ /**
2033
+ * Compile the per-viewer view of a row: the ground-truth canonical row with the
2034
+ * DERIVED observations the viewer is allowed to see folded on top (latest
2035
+ * audience-visible observation per attribute wins). A derived value is visible
2036
+ * only when the viewer can reach every source it came from, so un-sharing a
2037
+ * source reverts the value with no residue. `visibleSources` is the set of
2038
+ * source ids the viewer can see; omit it (or pass `'all'`) for the local
2039
+ * single-user case where you see everything. Returns null if the row is absent.
2040
+ */
2041
+ foldForViewer(table: string, id: PkLookup, opts?: {
2042
+ visibleSources?: Iterable<string> | 'all';
2043
+ keyStore?: SourceKeyStore;
2044
+ }): Promise<Row | null>;
2045
+ /** Open any crypto-sealed values in a derived observation's `changes`. Returns
2046
+ * the plaintext changes, or `null` if a sealed value can't be opened because
2047
+ * its source's key was shredded (the value is gone for good). Values that
2048
+ * aren't sealed pass through; with no key store, sealed values can't be read,
2049
+ * so the observation is dropped (returns null). */
2050
+ private _openSealedObservation;
1901
2051
  get(table: string, id: PkLookup): Promise<Row | null>;
1902
2052
  /**
1903
2053
  * Upsert a record by natural key. If a non-deleted record with the given
@@ -1969,76 +2119,6 @@ declare class Lattice {
1969
2119
  */
1970
2120
  search(table: string, query: string, opts?: SearchOptions): Promise<SearchResult[]>;
1971
2121
  query(table: string, opts?: QueryOptions): Promise<Row[]>;
1972
- /**
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()`.
1982
- *
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
2122
  count(table: string, opts?: CountOptions): Promise<number>;
2043
2123
  render(outputDir: string): Promise<RenderResult>;
2044
2124
  sync(outputDir: string): Promise<SyncResult>;
@@ -2167,10 +2247,31 @@ declare class Lattice {
2167
2247
  * shouldn't block the write completion.
2168
2248
  */
2169
2249
  private _syncEmbedding;
2170
- /** Create the __lattice_changelog table and index. */
2250
+ /**
2251
+ * Create the __lattice_changelog table and index. This is the single,
2252
+ * canonical change-log substrate (the dead `__lattice_change_log` team-sync
2253
+ * envelope was removed in 3.0). Beyond the field-level delta columns it
2254
+ * carries provenance columns for the per-viewer observation model:
2255
+ * `source_ref` (the source-set that informed a derived value),
2256
+ * `change_kind` (`ground_truth` | `derived`), `superseded_by`, `audience`
2257
+ * (defaults to row audience), and `source_sensitive` (crypto-shred flag).
2258
+ * All are additive + nullable (or defaulted) — Stage-0 metadata, no behavior
2259
+ * change until later stages read them.
2260
+ */
2261
+ /** Whether `__lattice_changelog` physically exists (read-only; no DDL), so a
2262
+ * scoped member can decide there are no observations without trying to create
2263
+ * the table. */
2264
+ private _changelogTableExists;
2171
2265
  private _ensureChangelogTable;
2172
- /** Append a changelog entry if the table has changelog enabled. */
2266
+ /** Append a changelog entry if the table has changelog enabled. The optional
2267
+ * `prov` carries the per-viewer observation provenance (source-set, kind,
2268
+ * audience, …); when omitted the entry behaves exactly as a pre-3.0 entry. */
2173
2269
  private _appendChangelog;
2270
+ /** The ungated change-log INSERT. `_appendChangelog` wraps it with the
2271
+ * changelog-enabled gate; `observe()` calls it directly (an observation is an
2272
+ * explicit, always-recorded write to the substrate). The change-log table must
2273
+ * exist already. */
2274
+ private _writeChangelogRow;
2174
2275
  /** Prune changelog entries based on retention policy. */
2175
2276
  private _pruneChangelog;
2176
2277
  /** Parse a raw changelog DB row into a ChangeEntry. */
@@ -2286,6 +2387,15 @@ interface LatticeFieldDef {
2286
2387
  * (e.g. `assignee_id: { ref: user }` → relation name `assignee`).
2287
2388
  */
2288
2389
  ref?: string;
2390
+ /**
2391
+ * Per-column audience (Stage-0 scaffolding for the per-viewer enrichment
2392
+ * model). Names who may see this column's value in a cloud. Omitted ⇒
2393
+ * `row-audience` — the value is visible to exactly whoever can see the row,
2394
+ * which is today's behavior, so leaving it unset changes nothing. Later
2395
+ * stages parse a richer grammar (e.g. `subject+role:hr`) and generate a
2396
+ * cell-masking view from it; Stage-0 only records the metadata.
2397
+ */
2398
+ audience?: string;
2289
2399
  }
2290
2400
  /**
2291
2401
  * Inline render spec inside YAML — a flat object alternative to `TemplateRenderSpec`.
@@ -3062,6 +3172,80 @@ declare function hashFile(srcPath: string): Promise<string>;
3062
3172
  */
3063
3173
  declare function attachBlob(srcPath: string, latticeRoot: string): Promise<BlobMetadata>;
3064
3174
 
3175
+ /**
3176
+ * S3 object storage for file blobs (cloud workspaces). Bytes uploaded to a cloud
3177
+ * are written to S3 under a content-addressed key so every member who can see the
3178
+ * `files` row can pull them down — see `docs/cloud.md`. `@aws-sdk/client-s3` is an
3179
+ * OPTIONAL dependency, lazy-imported the same way `sharp` is, so a build without it
3180
+ * still loads: callers catch {@link S3UnavailableError} and fall back to local-only
3181
+ * blobs rather than failing.
3182
+ *
3183
+ * Access control is NOT enforced here — it rides entirely on the `files`-row
3184
+ * Postgres RLS at the serve route (`files-routes.ts`): a member only ever learns a
3185
+ * key for a row they can SELECT, keys are unguessable (sha256), and the bucket
3186
+ * credential is least-privilege (GetObject/PutObject only, no ListBucket). See the
3187
+ * security notes in `docs/cloud.md`.
3188
+ */
3189
+ /** A minimal remote blob store — the only surface the upload + serve paths use. */
3190
+ interface RemoteBlobStore {
3191
+ /** Idempotent upload under `key` (same content ⇒ same key ⇒ same object). */
3192
+ put(key: string, body: Buffer, opts?: {
3193
+ contentType?: string;
3194
+ }): Promise<void>;
3195
+ /** Stream the object's bytes back (to pipe into an HTTP response). */
3196
+ get(key: string): Promise<NodeJS.ReadableStream>;
3197
+ /** Whether an object exists under `key`. */
3198
+ exists(key: string): Promise<boolean>;
3199
+ }
3200
+ /** Connection config for an S3 (or S3-compatible) bucket. Credentials are optional
3201
+ * — when omitted, the AWS default credential chain (env / shared config / IAM
3202
+ * role) is used. `endpoint` (+ path-style) targets R2 / MinIO / LocalStack. */
3203
+ interface S3StoreConfig {
3204
+ bucket: string;
3205
+ region: string;
3206
+ prefix: string;
3207
+ endpoint?: string;
3208
+ credentials?: {
3209
+ accessKeyId: string;
3210
+ secretAccessKey: string;
3211
+ };
3212
+ }
3213
+ /** Thrown when `@aws-sdk/client-s3` is not installed. Callers degrade to
3214
+ * local-only behavior; they do not 500. */
3215
+ declare class S3UnavailableError extends Error {
3216
+ constructor(message: string);
3217
+ }
3218
+ /** Content-addressed object key: `<prefix>/<sha256>` (POSIX separators, stable
3219
+ * across machines + idempotent). A leading/trailing slash on the prefix is
3220
+ * normalized away. */
3221
+ declare function s3Key(prefix: string, sha256: string): string;
3222
+ /**
3223
+ * Build a {@link RemoteBlobStore} backed by S3. Throws {@link S3UnavailableError}
3224
+ * if the optional `@aws-sdk/client-s3` dependency is absent — the lazy import
3225
+ * mirrors the `sharp` pattern so the module loads without it.
3226
+ */
3227
+ declare function createS3Store(cfg: S3StoreConfig): Promise<RemoteBlobStore>;
3228
+
3229
+ /**
3230
+ * The S3 config for a cloud workspace, resolved per-member. `enabled` gates the
3231
+ * whole feature; the rest is the {@link S3StoreConfig} the store consumes. Stored
3232
+ * machine-local + encrypted (see user-config `s3-config.enc`), or supplied via env
3233
+ * for headless / CI.
3234
+ */
3235
+ interface S3Config extends S3StoreConfig {
3236
+ enabled: boolean;
3237
+ }
3238
+ /** Extract the cloud workspace LABEL from a config's `db:` line
3239
+ * (`db: ${LATTICE_DB:label}`), so the S3 config can be keyed by it. Null when the
3240
+ * config isn't a labelled cloud connection. */
3241
+ declare function activeWorkspaceLabel(configPath: string): string | null;
3242
+ /**
3243
+ * Resolve the active workspace's S3 config: the per-member machine-local config
3244
+ * keyed by the workspace label, else the environment. Returns null (S3 off) when
3245
+ * neither is configured — so a non-cloud / S3-disabled workspace is untouched.
3246
+ */
3247
+ declare function resolveActiveS3Config(configPath: string | undefined): S3Config | null;
3248
+
3065
3249
  /**
3066
3250
  * Machine-local lattice user config — small files that live outside any
3067
3251
  * single Lattice DB so a user's identity, encryption master key, saved
@@ -3416,7 +3600,7 @@ declare function importLegacyUserConfig(root: string): MigrateResult;
3416
3600
  * local and cloud sources share one set of utilities.
3417
3601
  */
3418
3602
  type RefKind = 'blob' | 'local_ref' | 'cloud_ref';
3419
- type RefProvider = 'fs' | 'web' | 'gdrive';
3603
+ type RefProvider = 'fs' | 'web' | 'gdrive' | 's3';
3420
3604
  /** The subset of a `files` row the resolver reads. */
3421
3605
  interface FilesRow {
3422
3606
  id?: string;
@@ -3578,520 +3762,6 @@ declare function referenceUrl(url: string, opts?: {
3578
3762
  allowPrivate?: boolean;
3579
3763
  }): Promise<ReferenceMetadata>;
3580
3764
 
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
3765
  /**
4096
3766
  * Cloud migration — copy a Lattice's data from one backing store to
4097
3767
  * another. Used by the GUI's "Migrate to cloud" flow, but exported as
@@ -4172,58 +3842,350 @@ declare function openTargetLatticeForMigration(configPath: string, targetUrl: st
4172
3842
  declare function archiveLocalSqlite(dbPath: string): string;
4173
3843
 
4174
3844
  /**
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`.
3845
+ * Cloud-connect probe non-destructive inspection of a candidate target
3846
+ * Lattice (a Postgres URL the user wants to migrate to or join). Used by the
3847
+ * GUI's "Migrate to cloud" and "Join a cloud" flows to tell, before the user
3848
+ * commits, whether a URL points at a fresh database or an already-secured
3849
+ * Lattice cloud. Exported as public API so library consumers can pre-flight the
3850
+ * same check.
3851
+ *
3852
+ * A "cloud" in v3 is a shared Postgres database with Lattice RLS installed —
3853
+ * its tell is the bookkeeping table `__lattice_owners` (created by
3854
+ * `installCloudRls`). The probe opens the URL with `introspectOnly` (NO DDL, so
3855
+ * it works even when the caller holds a scoped, non-superuser member role), asks
3856
+ * Postgres whether that table exists, and closes. It never mutates the target.
4178
3857
  */
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
- };
3858
+ interface CloudProbeResult {
3859
+ /** True iff the URL could be opened (connected + authenticated). */
3860
+ reachable: boolean;
3861
+ /** Adapter dialect of the probed URL. */
3862
+ dialect: 'sqlite' | 'postgres';
3863
+ /**
3864
+ * True iff the target is an established Lattice cloud — Postgres with RLS
3865
+ * bookkeeping (`__lattice_owners`) present. A fresh Postgres (no Lattice
3866
+ * schema yet) and any SQLite file are `false`: the former is a migration
3867
+ * target, the latter is a private local store.
3868
+ */
3869
+ isCloud: boolean;
3870
+ /** Underlying error message when `reachable: false`. */
3871
+ error?: string;
4191
3872
  }
3873
+ /** Detect the RLS bookkeeping table without assuming SELECT privilege on it.
3874
+ * `to_regclass` returns the table's OID name when it exists and NULL when it
3875
+ * doesn't — and, unlike `SELECT FROM __lattice_owners`, it does not require any
3876
+ * privilege on the table, so a scoped member (who is denied SELECT on the
3877
+ * bookkeeping tables) still gets a truthful answer. */
3878
+ declare function cloudRlsInstalled(probe: Lattice): Promise<boolean>;
3879
+ /**
3880
+ * Whether the connected role may create other roles — the capability that
3881
+ * separates a cloud OWNER (ran the migration, owns the rows, can invite members)
3882
+ * from a scoped MEMBER (provisioned `NOCREATEROLE`). Read from
3883
+ * `pg_roles.rolcreaterole` for the live role. SQLite or any error → false.
3884
+ */
3885
+ declare function canManageRoles(db: Lattice): Promise<boolean>;
3886
+ /**
3887
+ * Probe a candidate Lattice URL for reachability + cloud status.
3888
+ *
3889
+ * Never throws. Errors are returned in the result's `error` field with
3890
+ * `reachable: false`.
3891
+ */
3892
+ declare function probeCloud(targetUrl: string): Promise<CloudProbeResult>;
3893
+
4192
3894
  /**
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.
3895
+ * True iff `url` parses as a `postgres://` / `postgresql://` URL. Used by
3896
+ * the GUI to distinguish a cloud (shared Postgres) connection from a local
3897
+ * SQLite file path.
4196
3898
  */
4197
3899
  declare function isPostgresUrl(url: string): boolean;
3900
+
4198
3901
  /**
4199
- * Direct-Postgres equivalent of the cloud's `POST /api/auth/register`.
3902
+ * One-time bootstrap for a cloud: the ownership bookkeeping tables and the shared
3903
+ * `SECURITY DEFINER` helpers. Idempotent (`CREATE TABLE IF NOT EXISTS`,
3904
+ * `CREATE OR REPLACE FUNCTION`). Multi-statement — Postgres-only, so it never hits
3905
+ * the single-statement SQLite migration path.
4200
3906
  *
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).
3907
+ * NOTE (follow-up): the `SECURITY DEFINER` helpers below should pin `search_path`
3908
+ * to the cloud schema to fully close the definer-search_path class of issue. Today
3909
+ * members are `NOSUPERUSER` without CREATE on the schema, so they cannot plant a
3910
+ * shadowing object; the pin is hardening, tracked for the schema-awareness pass.
3911
+ */
3912
+ /**
3913
+ * Group role every cloud member inherits. Table privileges are granted to the
3914
+ * group, so adding a shared table or a member is a single GRANT — while RLS still
3915
+ * filters rows per individual login role (`session_user`). The group grants
3916
+ * *access*, never *visibility*.
3917
+ */
3918
+ declare const MEMBER_GROUP = "lattice_members";
3919
+ /** Install the cloud RLS bootstrap (bookkeeping + helper functions). No-op on SQLite. */
3920
+ declare function installCloudRls(db: Lattice): Promise<void>;
3921
+ /**
3922
+ * Secure the observation substrate (`__lattice_changelog`) so a member reads
3923
+ * only what they're allowed to: a DERIVED observation only when it can reach
3924
+ * EVERY source it was derived from (so a hidden enrichment never reaches the
3925
+ * member — existence-hiding is structural), and a ground-truth / audit entry
3926
+ * only for a row that is itself visible to the member. Both predicates route
3927
+ * through the `session_user`-keyed SECURITY DEFINER helpers, so they bind to the
3928
+ * real member. `FORCE ROW LEVEL SECURITY` applies the policy even to the table
3929
+ * owner. No-op on SQLite (single-user; no cross-viewer leak to guard). Run after
3930
+ * the change-log table exists (`Lattice.ensureObservationSubstrate`).
3931
+ */
3932
+ declare function enableChangelogRls(db: Lattice): Promise<void>;
3933
+ /** Enable RLS on one shared table. No-op on SQLite. Idempotent via a per-table version key. */
3934
+ declare function enableRlsForTable(db: Lattice, table: string, pkCols: readonly string[]): Promise<void>;
3935
+ /**
3936
+ * Stamp the current role as owner of every row that already exists in a table —
3937
+ * for data migrated into a cloud BEFORE the ownership trigger existed (the
3938
+ * trigger only fires on new writes). Without this, migrated rows have no
3939
+ * ownership record and RLS would hide them from everyone. Idempotent; no-op on
3940
+ * SQLite or an unkeyable table.
3941
+ */
3942
+ declare function backfillOwnership(db: Lattice, table: string, pkCols: readonly string[]): Promise<void>;
3943
+
3944
+ /** A URL-safe random password (48 hex chars) for a new member role. */
3945
+ declare function generateMemberPassword(): string;
3946
+ /**
3947
+ * Derive a safe, unique Postgres role name from a free-form label (e.g. an email
3948
+ * or display name). Lowercased, non-word chars collapsed to `_`, prefixed so it
3949
+ * always starts legally and namespaced under `lm_`, with a short random suffix so
3950
+ * two people with similar labels never collide.
3951
+ */
3952
+ declare function memberRoleName(label: string): string;
3953
+ /**
3954
+ * Create (or re-key) a scoped member LOGIN role and add it to the member group.
3955
+ * Idempotent on the role's existence: a re-invite rotates the password. Requires
3956
+ * the connection to hold `CREATEROLE`. After this, the member connects with
3957
+ * `postgres://<role>:<password>@<host>/<db>` and sees only its permitted rows.
3958
+ */
3959
+ declare function provisionMemberRole(db: Lattice, role: string, password: string): Promise<void>;
3960
+ /**
3961
+ * Change a row's sharing through the owner-only `lattice_set_row_visibility`
3962
+ * SECURITY DEFINER function. Only the row's owner (Postgres raises for anyone
3963
+ * else, enforced inside the function) may call it. `pk` is the row's canonical
3964
+ * primary-key string — a single-column key is the bare value; a composite key is
3965
+ * its columns joined by TAB, matching Lattice's serialization.
3966
+ */
3967
+ declare function setRowVisibility(db: Lattice, table: string, pk: string, visibility: string): Promise<void>;
3968
+ /**
3969
+ * Per-card audience override: grant (or revoke) one member access to ONE masked
3970
+ * cell — a specific (table, pk, column) — without changing the column's
3971
+ * schema-level audience. Owner-only (the SQL function raises for a non-owner).
3972
+ * `pk` is the row's canonical primary-key string.
3973
+ */
3974
+ declare function grantCell(db: Lattice, table: string, pk: string, column: string, grantee: string): Promise<void>;
3975
+ declare function revokeCell(db: Lattice, table: string, pk: string, column: string, grantee: string): Promise<void>;
3976
+ /**
3977
+ * Remove a member: clear its privileges and drop the role. NOTE: rows the member
3978
+ * owned remain in their tables but become unreachable (their `owner_role` no
3979
+ * longer matches any login role, and RLS shows a row only to its owner / grantees
3980
+ * / everyone) — reassigning or purging a departed member's rows is a separate,
3981
+ * deliberate step, not a side effect of revoking access.
3982
+ */
3983
+ declare function revokeMemberRole(db: Lattice, role: string): Promise<void>;
3984
+
3985
+ /**
3986
+ * Physical-schema discovery for cloud members. A member connects to a shared
3987
+ * cloud as a scoped role whose local config may declare NO entities — yet the
3988
+ * GUI must show every table the member is allowed to use. Postgres only exposes
3989
+ * a table in `pg_tables` / `information_schema` to a role that holds a privilege
3990
+ * on it, so listing the role's visible user tables is exactly the set RLS lets
3991
+ * it touch: the member's granted tables, never another's bookkeeping.
3992
+ */
3993
+ interface DiscoveredTable {
3994
+ name: string;
3995
+ columns: string[];
3996
+ /** Primary-key column(s), in key order. May be empty for a keyless table. */
3997
+ pk: string[];
3998
+ }
3999
+ /**
4000
+ * List the user tables a member's role is actually privileged to use, excluding
4001
+ * Lattice/GUI bookkeeping (anything starting `_`). `information_schema.tables`
4002
+ * is privilege-filtered — it shows a role only the tables it holds a grant on or
4003
+ * owns — so this returns exactly the member's reachable set, never another
4004
+ * member's bookkeeping (which is granted to no one). Scoped to `current_schema()`
4005
+ * so it follows the connection's search_path (the cloud's `public` in production).
4006
+ * Returns each table's columns + primary key so the caller can register it.
4007
+ * Postgres-only — returns `[]` on SQLite (a private, single-user store).
4008
+ */
4009
+ declare function discoverCloudTables(db: Lattice): Promise<DiscoveredTable[]>;
4010
+
4011
+ /** True when this audience means "no mask" (visible to whoever can see the row). */
4012
+ declare function isRowAudience(audience: string | undefined): boolean;
4013
+ /**
4014
+ * Compile a column `audience` spec into a boolean SQL predicate over the helper
4015
+ * functions. Returns `'true'` for the row-audience / everyone case. Throws on an
4016
+ * unknown or malformed clause.
4017
+ */
4018
+ declare function audiencePredicate(audience: string): string;
4019
+ /** Whether a table needs a masking view at all (any column has a real audience). */
4020
+ declare function tableNeedsAudienceView(columnAudience: Record<string, string>): boolean;
4021
+ /**
4022
+ * SQL to (re)generate a table's cell-masking view, point members at it, and make
4023
+ * the base table's columns unreachable to members so the mask can't be bypassed:
4207
4024
  *
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.
4025
+ * - `CREATE OR REPLACE VIEW <t>_v` every column passes through, except
4026
+ * audience columns which become `CASE WHEN <predicate> THEN col END`.
4027
+ * - The view re-applies ROW visibility with `WHERE lattice_row_visible(t, pk)`.
4028
+ * This is essential: the view runs with its OWNER's rights, so the base
4029
+ * table's RLS would be evaluated as the owner (who sees everything). The
4030
+ * `session_user`-keyed SECURITY DEFINER helper re-binds row filtering to the
4031
+ * real member, so an owner-defined view still filters per viewer.
4032
+ * - `GRANT SELECT` on the view + `REVOKE SELECT` on the base from members: a
4033
+ * member reads only the masked, row-filtered view and cannot reach the raw
4034
+ * column. (Member writes to such a table flow through the observation path —
4035
+ * members keep INSERT/UPDATE/DELETE on the base under RLS; only SELECT moves
4036
+ * to the view.)
4213
4037
  *
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.
4038
+ * Idempotent. `columns` is the table's full column list (stable order); `pkCols`
4039
+ * its primary key, so the row filter matches the RLS policy's pk serialization.
4040
+ */
4041
+ declare function audienceViewSql(table: string, columns: readonly string[], pkCols: readonly string[], columnAudience: Record<string, string>): string;
4042
+ /**
4043
+ * Generate + install a table's cell-masking view (Postgres only; no-op on SQLite
4044
+ * and on a table with no audience columns). Versioned by a content hash of the
4045
+ * columns / pk / column-audience so a changed spec regenerates and an unchanged
4046
+ * one is skipped. Run AFTER the table + RLS exist (the view reuses the row
4047
+ * visibility helper and revokes the base SELECT that enableRlsForTable granted).
4048
+ */
4049
+ declare function enableAudienceView(db: Lattice, table: string, columns: readonly string[], pkCols: readonly string[], columnAudience: Record<string, string>): Promise<void>;
4050
+
4051
+ /**
4052
+ * The per-viewer fold (the "local compile" of the per-viewer enrichment model).
4217
4053
  *
4218
- * On success returns the same shape the HTTP route returns so a
4219
- * direct-postgres register can mirror the hosted register path.
4054
+ * Source-gated enrichment is per-viewer: a value derived from a file that one
4055
+ * member can't see must not appear for that member. Postgres RLS + the generated
4056
+ * mask view handle row visibility and fixed-policy columns; this handles the
4057
+ * remaining case — a column whose VALUE differs by which sources you can reach.
4220
4058
  *
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).
4059
+ * The compile is a deterministic, programmatic fold (NOT AI): start from the
4060
+ * broadly-visible ground-truth projection, then replay the observations the
4061
+ * viewer is allowed to see latest audience-visible observation per attribute
4062
+ * wins. Because observations are additive and only the viewer-visible ones
4063
+ * contribute, the fold is provably leak-free (sound only for additive/monotonic
4064
+ * derivations) and revocation is structural: drop a viewer's access to a source
4065
+ * and every value derived from it silently reverts to the prior visible
4066
+ * observation (or ground truth), with no residue — no promotion, no copy left
4067
+ * behind. Run on the member's local replica over already-audience-gated
4068
+ * observations, so hidden observations never reach them (existence-hiding is
4069
+ * structural) and egress is paid once at pull, never per read.
4070
+ */
4071
+ /** One attribute-level observation — a single column's value with its provenance. */
4072
+ interface Observation {
4073
+ /** The column this observation sets. */
4074
+ attribute: string;
4075
+ /** The value it sets the column to. */
4076
+ value: unknown;
4077
+ /** Ordering key (ISO timestamp). Latest visible observation per attribute wins. */
4078
+ createdAt: string;
4079
+ /** `ground_truth` (always visible) or `derived` (gated by its source-set). */
4080
+ changeKind?: 'ground_truth' | 'derived' | null;
4081
+ /** Source ids that produced a derived value. The observation is visible to a
4082
+ * viewer only if the viewer can see EVERY one of them (intersection-of-sources
4083
+ * reader set — losing any source hides the derived value). */
4084
+ sourceRef?: readonly string[] | null;
4085
+ }
4086
+ /** What a given member can reach, for deciding observation visibility. */
4087
+ interface Viewer {
4088
+ /** Source ids (file primary keys) this member can currently see. */
4089
+ visibleSources: ReadonlySet<string>;
4090
+ }
4091
+ /**
4092
+ * Whether a viewer may see an observation. Ground-truth is always visible (it is
4093
+ * the broadly-shared projection). A derived observation is visible iff the viewer
4094
+ * can see every source it was derived from — so un-sharing or deleting any one
4095
+ * source drops it. An unsourced derived observation is treated as hidden (fail
4096
+ * closed): a derived value with no recorded provenance can't be proven safe.
4097
+ */
4098
+ declare function observationVisible(obs: Observation, viewer: Viewer): boolean;
4099
+ /**
4100
+ * Compile one per-viewer entity: overlay the viewer-visible observations onto the
4101
+ * ground-truth projection, latest-per-attribute winning. Pure + deterministic —
4102
+ * the same (ground, observations, viewer) always yields the same row, and an
4103
+ * observation the viewer can't see never affects the result.
4104
+ */
4105
+ declare function foldEntity(ground: Row, observations: readonly Observation[], viewer: Viewer): Row;
4106
+ /**
4107
+ * Expand a change-log row's `changes` object into per-attribute observations.
4108
+ * The change-log records one row per write with a JSON `changes` map; the fold
4109
+ * works per attribute, so each changed field becomes its own observation carrying
4110
+ * the write's provenance. (Caller supplies the already-parsed change-log entry.)
4111
+ */
4112
+ declare function observationsFromChange(entry: {
4113
+ changes: Record<string, unknown> | null;
4114
+ createdAt: string;
4115
+ changeKind?: 'ground_truth' | 'derived' | null;
4116
+ sourceRef?: readonly string[] | null;
4117
+ }): Observation[];
4118
+
4119
+ declare class FoldCache {
4120
+ private readonly cache;
4121
+ /** Compiled entity for (rowId, viewer), computing + caching it on a miss. */
4122
+ get(rowId: string, ground: Row, observations: readonly Observation[], viewer: Viewer): Row;
4123
+ /** Drop every cached version of a row — call when a new observation lands for
4124
+ * it (any viewer's compile may have changed). Cheap, exact, no over-eviction
4125
+ * of other rows. */
4126
+ invalidateRow(rowId: string): void;
4127
+ /** Drop everything (e.g. on a full replica re-pull). */
4128
+ clear(): void;
4129
+ /** Number of cached (row, viewer) compilations — for tests / introspection. */
4130
+ get size(): number;
4131
+ }
4132
+
4133
+ /**
4134
+ * Turn a Postgres database into a secured Lattice cloud, in place: install the
4135
+ * RLS bootstrap + the observation substrate, then for every registered user
4136
+ * table stamp the current role as owner of the existing rows and force RLS (plus
4137
+ * a cell-masking view for any audience columns). Idempotent and additive — safe
4138
+ * to run on a fresh migration target OR on an already-populated Postgres that
4139
+ * isn't a cloud yet (the "secure this cloud" cutover). No-op on SQLite.
4140
+ *
4141
+ * Must run as a role that owns the tables and can create roles (a cloud
4142
+ * owner / DBA). `backfillOwnership` runs BEFORE `enableRlsForTable` so a
4143
+ * non-superuser owner can still SELECT every row to stamp it before FORCE RLS
4144
+ * filters the table to rows it already owns.
4145
+ */
4146
+ declare function secureCloud(db: Lattice): Promise<void>;
4147
+
4148
+ /**
4149
+ * Workspace-level settings for a cloud — cloud-wide values the OWNER controls and
4150
+ * members never see in the product surface. Stored in `__lattice_cloud_settings`,
4151
+ * a bookkeeping table members have no grant on — so its VALUE is unreadable to a
4152
+ * member (SELECT is denied, like every other `__lattice_*` table; the System view
4153
+ * may still list the table's existence + column names from the catalog, but never
4154
+ * its contents). It is reached only through two `SECURITY DEFINER` helpers:
4155
+ *
4156
+ * - `lattice_get_cloud_setting(key)` — readable by members, because a member's
4157
+ * own chat must inject the value (the chat call is assembled in each member's
4158
+ * LOCAL gui process). This is the deliberate, documented ceiling: secrecy is
4159
+ * **app-mediated** (hidden from the UI + every API response), NOT cryptographic
4160
+ * — a member CAN read the value from their own session if they go looking.
4161
+ * - `lattice_set_cloud_setting(key, value)` — owner-only (RAISEs unless the
4162
+ * caller can create roles, the same gate as `lattice_assign_role`).
4163
+ *
4164
+ * Postgres-only: a local SQLite workspace is single-user, so there is nothing to
4165
+ * keep secret and these are all no-ops / null there.
4166
+ */
4167
+ /** Setting key for the chat system prompt an owner bundles into every member's chat. */
4168
+ declare const CLOUD_SETTING_SYSTEM_PROMPT = "chat_system_prompt";
4169
+ /**
4170
+ * Install the workspace-settings table + helpers. Idempotent (`CREATE TABLE IF
4171
+ * NOT EXISTS` / `CREATE OR REPLACE FUNCTION`). No-op on SQLite. Run as the cloud
4172
+ * owner — `secureCloud` calls it for new clouds, and the owner-only settings
4173
+ * endpoint calls it lazily so an already-secured cloud picks it up on first use.
4174
+ */
4175
+ declare function installCloudSettings(db: Lattice): Promise<void>;
4176
+ /**
4177
+ * Read a cloud workspace setting via the SECURITY DEFINER getter. Best-effort:
4178
+ * returns null on SQLite, on a cloud that hasn't installed the helper yet (the
4179
+ * function is absent), or on any error — so a caller treats "unset" and "couldn't
4180
+ * read" identically (e.g. the chat path simply injects nothing).
4181
+ */
4182
+ declare function getCloudSetting(db: Lattice, key: string): Promise<string | null>;
4183
+ /**
4184
+ * Owner-only: write a cloud workspace setting via the SECURITY DEFINER setter.
4185
+ * The function RAISEs if the caller isn't a cloud owner; that surfaces here as a
4186
+ * thrown error which the (already owner-gated) endpoint reports. Not silent.
4225
4187
  */
4226
- declare function registerDirectViaPostgres(cloudUrl: string, email: string, name: string, teamName: string): Promise<DirectRegisterResult>;
4188
+ declare function setCloudSetting(db: Lattice, key: string, value: string): Promise<void>;
4227
4189
 
4228
4190
  /** A content block in the Anthropic message format used here. */
4229
4191
  type ContentBlock = {
@@ -4514,4 +4476,4 @@ interface PdfOptions {
4514
4476
  */
4515
4477
  declare function describePdf(auth: ClaudeAuth, path: string, opts?: PdfOptions): Promise<string>;
4516
4478
 
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 };
4479
+ export { type AddWorkspaceOptions, type AdoptNativeOptions, type AdoptResult, type ApplyWriteResult, 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, 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 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 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 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, getWorkspace, grantCell, hasFtsIndex, hashFile, importLegacyUserConfig, installCloudRls, installCloudSettings, isEncrypted, isNativeEntity, isPostgresUrl, isPrivateIp, isRowAudience, isV1EntityFiles, listDbCredentials, listNativeBindings, listTokens, listWorkspaces, 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, registerNativeEntities, registryPath, resolveActiveS3Config, resolveLatticeRoot, resolveSource, resolveWorkspacePaths, revokeCell, revokeMemberRole, rootConfigDir, s3Key, saveDbCredential, saveDbCredentialForTeam, sealUnderSource, secureCloud, setActiveWorkspace, setCloudSetting, setRowVisibility, shredSource, slugify, summarizeText, tableNeedsAudienceView, toSafeDirName, truncate, validateEntryId, workspaceBlobsDir, workspaceConfigPath, workspaceContextDir, workspaceDataDir, workspaceDbPath, workspaceDir, workspacesDir, writeIdentity, writeManifest, writePreferences, writeRegistry, writeToken };