latticesql 3.0.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.
package/dist/index.cjs CHANGED
@@ -39405,6 +39405,7 @@ __export(index_exports, {
39405
39405
  NATIVE_ENTITY_NAMES: () => NATIVE_ENTITY_NAMES,
39406
39406
  NATIVE_REGISTRY_TABLE: () => NATIVE_REGISTRY_TABLE,
39407
39407
  PostgresAdapter: () => PostgresAdapter,
39408
+ ProgressThrottle: () => ProgressThrottle,
39408
39409
  READ_ONLY_HEADER: () => READ_ONLY_HEADER,
39409
39410
  ROOT_DIRNAME: () => ROOT_DIRNAME,
39410
39411
  ReferenceUnavailableError: () => ReferenceUnavailableError,
@@ -39468,6 +39469,7 @@ __export(index_exports, {
39468
39469
  getCloudSetting: () => getCloudSetting,
39469
39470
  getDbCredential: () => getDbCredential,
39470
39471
  getOrCreateMasterKey: () => getOrCreateMasterKey,
39472
+ getTablePolicy: () => getTablePolicy,
39471
39473
  getWorkspace: () => getWorkspace,
39472
39474
  grantCell: () => grantCell,
39473
39475
  hasFtsIndex: () => hasFtsIndex,
@@ -39485,6 +39487,7 @@ __export(index_exports, {
39485
39487
  listNativeBindings: () => listNativeBindings,
39486
39488
  listTokens: () => listTokens,
39487
39489
  listWorkspaces: () => listWorkspaces,
39490
+ loadColumnPolicy: () => loadColumnPolicy,
39488
39491
  manifestPath: () => manifestPath,
39489
39492
  markdownTable: () => markdownTable,
39490
39493
  memberRoleName: () => memberRoleName,
@@ -39512,6 +39515,7 @@ __export(index_exports, {
39512
39515
  readToken: () => readToken,
39513
39516
  referenceLocalFile: () => referenceLocalFile,
39514
39517
  referenceUrl: () => referenceUrl,
39518
+ regenerateAudienceViewFromDb: () => regenerateAudienceViewFromDb,
39515
39519
  registerNativeEntities: () => registerNativeEntities,
39516
39520
  registryPath: () => registryPath,
39517
39521
  resolveActiveS3Config: () => resolveActiveS3Config,
@@ -39526,9 +39530,13 @@ __export(index_exports, {
39526
39530
  saveDbCredentialForTeam: () => saveDbCredentialForTeam,
39527
39531
  sealUnderSource: () => sealUnderSource,
39528
39532
  secureCloud: () => secureCloud,
39533
+ seedColumnPolicyFromYaml: () => seedColumnPolicyFromYaml,
39529
39534
  setActiveWorkspace: () => setActiveWorkspace,
39530
39535
  setCloudSetting: () => setCloudSetting,
39536
+ setColumnAudience: () => setColumnAudience,
39531
39537
  setRowVisibility: () => setRowVisibility,
39538
+ setTableDefaultVisibility: () => setTableDefaultVisibility,
39539
+ setTableNeverShare: () => setTableNeverShare,
39532
39540
  shredSource: () => shredSource,
39533
39541
  slugify: () => slugify,
39534
39542
  summarizeText: () => summarizeText,
@@ -41616,7 +41624,42 @@ function cleanupEntityContexts(outputDir, entityContexts, currentSlugsByTable, m
41616
41624
  return result;
41617
41625
  }
41618
41626
 
41627
+ // src/render/progress.ts
41628
+ var THROTTLE_WINDOW_MS = 200;
41629
+ var ProgressThrottle = class {
41630
+ cb;
41631
+ windowMs;
41632
+ lastEmit = 0;
41633
+ constructor(cb, windowMs = THROTTLE_WINDOW_MS) {
41634
+ this.cb = cb;
41635
+ this.windowMs = windowMs;
41636
+ }
41637
+ /**
41638
+ * Emit a `table-progress` event, but only if the window since the last
41639
+ * passthrough has elapsed. Dropped events are simply not delivered — the next
41640
+ * one that survives carries the latest running count.
41641
+ */
41642
+ tick(event) {
41643
+ if (!this.cb) return;
41644
+ const now = Date.now();
41645
+ if (now - this.lastEmit < this.windowMs) return;
41646
+ this.lastEmit = now;
41647
+ this.cb(event);
41648
+ }
41649
+ /**
41650
+ * Emit a lifecycle event immediately and reset the throttle window. Use for
41651
+ * `table-start`, `table-done`, `done`, and `error` — none of which should
41652
+ * ever be dropped. Resetting on `table-start` gives each table a clean budget.
41653
+ */
41654
+ force(event) {
41655
+ if (!this.cb) return;
41656
+ this.lastEmit = Date.now();
41657
+ this.cb(event);
41658
+ }
41659
+ };
41660
+
41619
41661
  // src/render/engine.ts
41662
+ var YIELD_EVERY_ENTITIES = 200;
41620
41663
  var NOOP_RENDER = () => "";
41621
41664
  var RenderEngine = class {
41622
41665
  _schema;
@@ -41630,11 +41673,14 @@ var RenderEngine = class {
41630
41673
  this._getTaskContext = getTaskContext ?? (() => "");
41631
41674
  this._skipEmpty = options?.skipEmpty ?? false;
41632
41675
  }
41633
- async render(outputDir) {
41676
+ async render(outputDir, opts = {}) {
41634
41677
  const start = Date.now();
41635
41678
  const filesWritten = [];
41636
41679
  const counters = { skipped: 0 };
41680
+ const signal = opts.signal;
41681
+ const throttle = new ProgressThrottle(opts.onProgress);
41637
41682
  for (const [name, def] of this._schema.getTables()) {
41683
+ if (signal?.aborted) return this._abortedResult(filesWritten, counters, start);
41638
41684
  if (this._skipEmpty && def.render === NOOP_RENDER) continue;
41639
41685
  let rows = await this._schema.queryTable(this._adapter, name);
41640
41686
  if (def.relevanceFilter) {
@@ -41676,8 +41722,18 @@ var RenderEngine = class {
41676
41722
  } else {
41677
41723
  counters.skipped++;
41678
41724
  }
41725
+ throttle.force({
41726
+ kind: "table-done",
41727
+ table: name,
41728
+ entitiesRendered: rows.length,
41729
+ entitiesTotal: rows.length,
41730
+ tableIndex: 0,
41731
+ tableCount: 0,
41732
+ pct: 100
41733
+ });
41679
41734
  }
41680
- for (const [, def] of this._schema.getMultis()) {
41735
+ for (const [name, def] of this._schema.getMultis()) {
41736
+ if (signal?.aborted) return this._abortedResult(filesWritten, counters, start);
41681
41737
  const keys = await def.keys();
41682
41738
  const tables = {};
41683
41739
  if (def.tables) {
@@ -41694,12 +41750,26 @@ var RenderEngine = class {
41694
41750
  counters.skipped++;
41695
41751
  }
41696
41752
  }
41753
+ throttle.force({
41754
+ kind: "table-done",
41755
+ table: name,
41756
+ entitiesRendered: keys.length,
41757
+ entitiesTotal: keys.length,
41758
+ tableIndex: 0,
41759
+ tableCount: 0,
41760
+ pct: 100
41761
+ });
41697
41762
  }
41698
41763
  const entityContextManifest = await this._renderEntityContexts(
41699
41764
  outputDir,
41700
41765
  filesWritten,
41701
- counters
41766
+ counters,
41767
+ throttle,
41768
+ signal
41702
41769
  );
41770
+ if (entityContextManifest === null) {
41771
+ return this._abortedResult(filesWritten, counters, start);
41772
+ }
41703
41773
  if (this._schema.getEntityContexts().size > 0) {
41704
41774
  writeManifest(outputDir, {
41705
41775
  version: 2,
@@ -41707,6 +41777,29 @@ var RenderEngine = class {
41707
41777
  entityContexts: entityContextManifest
41708
41778
  });
41709
41779
  }
41780
+ const result = {
41781
+ filesWritten,
41782
+ filesSkipped: counters.skipped,
41783
+ durationMs: Date.now() - start
41784
+ };
41785
+ throttle.force({
41786
+ kind: "done",
41787
+ table: null,
41788
+ entitiesRendered: 0,
41789
+ entitiesTotal: 0,
41790
+ tableIndex: 0,
41791
+ tableCount: 0,
41792
+ pct: 100,
41793
+ durationMs: result.durationMs
41794
+ });
41795
+ return result;
41796
+ }
41797
+ /**
41798
+ * Build the partial RenderResult to return when a render is aborted. No
41799
+ * `done` event is emitted — the caller treats abort as "discard the partial
41800
+ * tree", not as a successful completion.
41801
+ */
41802
+ _abortedResult(filesWritten, counters, start) {
41710
41803
  return {
41711
41804
  filesWritten,
41712
41805
  filesSkipped: counters.skipped,
@@ -41742,18 +41835,35 @@ var RenderEngine = class {
41742
41835
  /**
41743
41836
  * Render all entity context definitions.
41744
41837
  * Mutates `filesWritten` and `counters` in place.
41745
- * Returns manifest data for the entity contexts rendered this cycle.
41838
+ * Returns manifest data for the entity contexts rendered this cycle, or
41839
+ * `null` if the render was aborted mid-flight (the caller discards the
41840
+ * partial tree). Progress is reported through `throttle`; abort is observed
41841
+ * via `signal`.
41746
41842
  */
41747
- async _renderEntityContexts(outputDir, filesWritten, counters) {
41843
+ async _renderEntityContexts(outputDir, filesWritten, counters, throttle, signal) {
41748
41844
  const manifestData = {};
41749
41845
  const protectedTables = /* @__PURE__ */ new Set();
41750
41846
  for (const [t8, d6] of this._schema.getEntityContexts()) {
41751
41847
  if (d6.protected) protectedTables.add(t8);
41752
41848
  }
41753
- for (const [table, def] of this._schema.getEntityContexts()) {
41849
+ const entityTables = [...this._schema.getEntityContexts()];
41850
+ const tableCount = entityTables.length;
41851
+ for (let tableIndex = 0; tableIndex < tableCount; tableIndex++) {
41852
+ if (signal?.aborted) return null;
41853
+ const [table, def] = entityTables[tableIndex];
41754
41854
  const entityPk = this._schema.getPrimaryKey(table)[0] ?? "id";
41755
41855
  const allRows = await this._schema.queryTable(this._adapter, table);
41756
41856
  const directoryRoot = def.directoryRoot ?? table;
41857
+ const entitiesTotal = allRows.length;
41858
+ throttle.force({
41859
+ kind: "table-start",
41860
+ table,
41861
+ entitiesRendered: 0,
41862
+ entitiesTotal,
41863
+ tableIndex,
41864
+ tableCount,
41865
+ pct: 0
41866
+ });
41757
41867
  const manifestEntry = {
41758
41868
  directoryRoot,
41759
41869
  ...def.index ? { indexFile: def.index.outputFile } : {},
@@ -41769,7 +41879,12 @@ var RenderEngine = class {
41769
41879
  counters.skipped++;
41770
41880
  }
41771
41881
  }
41772
- for (const entityRow of allRows) {
41882
+ for (let i6 = 0; i6 < allRows.length; i6++) {
41883
+ const entityRow = allRows[i6];
41884
+ if (signal?.aborted) return null;
41885
+ if (i6 > 0 && i6 % YIELD_EVERY_ENTITIES === 0) {
41886
+ await new Promise((r6) => setImmediate(r6));
41887
+ }
41773
41888
  const rawSlug = def.slug(entityRow);
41774
41889
  const slug = rawSlug.replace(/[\u00A0\u2000-\u200B\u202F\u205F\u3000]/g, " ").replace(/[\u0000-\u001F\u007F]/g, "");
41775
41890
  if (/[^a-zA-Z0-9.\-_ @(),#&'+:;!~[\]]/.test(slug)) {
@@ -41814,6 +41929,7 @@ var RenderEngine = class {
41814
41929
  const entityFileHashes = {};
41815
41930
  const protection = protectedTables.size > 0 ? { protectedTables, currentTable: table } : void 0;
41816
41931
  for (const [filename, spec] of Object.entries(def.files)) {
41932
+ if (signal?.aborted) return null;
41817
41933
  const mergeDefaults = def.sourceDefaults && spec.source.type !== "self" && spec.source.type !== "custom" && spec.source.type !== "enriched";
41818
41934
  const source = mergeDefaults ? { ...def.sourceDefaults, ...spec.source } : spec.source;
41819
41935
  const rows = await resolveEntitySource(
@@ -41861,8 +41977,27 @@ var RenderEngine = class {
41861
41977
  }
41862
41978
  }
41863
41979
  manifestEntry.entities[slug] = entityFileHashes;
41980
+ const entitiesRendered = i6 + 1;
41981
+ throttle.tick({
41982
+ kind: "table-progress",
41983
+ table,
41984
+ entitiesRendered,
41985
+ entitiesTotal,
41986
+ tableIndex,
41987
+ tableCount,
41988
+ pct: entitiesTotal > 0 ? entitiesRendered / entitiesTotal * 100 : 100
41989
+ });
41864
41990
  }
41865
41991
  manifestData[table] = manifestEntry;
41992
+ throttle.force({
41993
+ kind: "table-done",
41994
+ table,
41995
+ entitiesRendered: entitiesTotal,
41996
+ entitiesTotal,
41997
+ tableIndex,
41998
+ tableCount,
41999
+ pct: 100
42000
+ });
41866
42001
  }
41867
42002
  return manifestData;
41868
42003
  }
@@ -44461,6 +44596,54 @@ var Lattice = class _Lattice {
44461
44596
  async insert(table, row, provenance) {
44462
44597
  const notInit = this._notInitError();
44463
44598
  if (notInit) return notInit;
44599
+ const { sql, values, pkValue, rowWithPk } = this._prepareInsert(table, row);
44600
+ await runAsyncOrSync(this._adapter, sql, values);
44601
+ await this._afterInsert(table, pkValue, rowWithPk, provenance);
44602
+ return pkValue;
44603
+ }
44604
+ /**
44605
+ * Insert a row while atomically forcing its cloud row-visibility, regardless of
44606
+ * the table's `default_row_visibility`. The per-table insert trigger reads a
44607
+ * transaction-local GUC (`lattice.force_row_visibility`); we set it and run the
44608
+ * INSERT inside a single transaction, so the row is stamped at `visibility` the
44609
+ * instant it exists — it is never momentarily visible at the table default, and
44610
+ * the change-feed `NOTIFY` (delivered only at COMMIT) fires when the row already
44611
+ * carries this visibility. This closes the create-then-demote window that a
44612
+ * plain `insert()` + `setRowVisibility()` would leave open.
44613
+ *
44614
+ * Postgres-only: SQLite is single-user (no cross-viewer leak) and has no trigger
44615
+ * to read the GUC, so it degrades to a plain {@link insert}. A `never_share`
44616
+ * table still wins — its rows are forced private even if `visibility` is
44617
+ * `'everyone'` (the trigger enforces that precedence).
44618
+ *
44619
+ * @since 3.1.0
44620
+ */
44621
+ async insertForcingVisibility(table, row, visibility, provenance) {
44622
+ const notInit = this._notInitError();
44623
+ if (notInit) return notInit;
44624
+ const vis = visibility;
44625
+ if (vis !== "private" && vis !== "everyone") {
44626
+ throw new Error(`lattice: invalid forced visibility "${vis}"`);
44627
+ }
44628
+ const withClient = this._adapter.withClient?.bind(this._adapter);
44629
+ if (this.getDialect() !== "postgres" || !withClient) {
44630
+ return this.insert(table, row, provenance);
44631
+ }
44632
+ const { sql, values, pkValue, rowWithPk } = this._prepareInsert(table, row);
44633
+ await withClient(async (tx) => {
44634
+ await tx.run(`SELECT set_config('lattice.force_row_visibility', ?, true)`, [visibility]);
44635
+ await tx.run(sql, values);
44636
+ });
44637
+ await this._afterInsert(table, pkValue, rowWithPk, provenance);
44638
+ return pkValue;
44639
+ }
44640
+ /**
44641
+ * Build the INSERT statement + canonical pk for a row (sanitize → schema-filter →
44642
+ * auto-pk → encrypt). Shared by {@link insert} and {@link insertForcingVisibility}
44643
+ * so both produce byte-identical writes; the latter only differs in running it
44644
+ * inside a GUC-scoped transaction.
44645
+ */
44646
+ _prepareInsert(table, row) {
44464
44647
  this._assertIdent(table);
44465
44648
  const sanitized = this._filterToSchemaColumns(table, this._sanitizer.sanitizeRow(row));
44466
44649
  const pkCols = this._schema.getPrimaryKey(table);
@@ -44476,12 +44659,17 @@ var Lattice = class _Lattice {
44476
44659
  const cols = Object.keys(encrypted).map((c6) => `"${c6}"`).join(", ");
44477
44660
  const placeholders = Object.keys(encrypted).map(() => "?").join(", ");
44478
44661
  const values = Object.values(encrypted);
44479
- await runAsyncOrSync(
44480
- this._adapter,
44481
- `INSERT INTO "${table}" (${cols}) VALUES (${placeholders})`,
44482
- values
44483
- );
44484
44662
  const pkValue = this._serializeRowPk(table, rowWithPk);
44663
+ return {
44664
+ sql: `INSERT INTO "${table}" (${cols}) VALUES (${placeholders})`,
44665
+ values,
44666
+ pkValue,
44667
+ rowWithPk
44668
+ };
44669
+ }
44670
+ /** Post-insert side effects (changelog, audit, write hooks, embedding sync),
44671
+ * identical for the plain and force-visibility insert paths. */
44672
+ async _afterInsert(table, pkValue, rowWithPk, provenance) {
44485
44673
  await this._appendChangelog(
44486
44674
  table,
44487
44675
  pkValue,
@@ -44495,7 +44683,6 @@ var Lattice = class _Lattice {
44495
44683
  this._sanitizer.emitAudit(table, "insert", pkValue);
44496
44684
  await this._fireWriteHooks(table, "insert", rowWithPk, pkValue);
44497
44685
  this._syncEmbedding(table, "insert", rowWithPk, pkValue);
44498
- return pkValue;
44499
44686
  }
44500
44687
  /**
44501
44688
  * Insert a row and return the full inserted row (including auto-generated
@@ -44562,6 +44749,7 @@ var Lattice = class _Lattice {
44562
44749
  const sanitized = this._filterToSchemaColumns(table, this._sanitizer.sanitizeRow(row));
44563
44750
  const encrypted = this._encryptRow(table, sanitized);
44564
44751
  const setCols = Object.keys(encrypted).map((c6) => `"${c6}" = ?`).join(", ");
44752
+ if (setCols === "") return;
44565
44753
  const { clause, params: pkParams } = this._pkWhere(table, id);
44566
44754
  let previousValues = null;
44567
44755
  if (this._changelogTables.has(table)) {
@@ -45178,13 +45366,32 @@ var Lattice = class _Lattice {
45178
45366
  // -------------------------------------------------------------------------
45179
45367
  // Sync
45180
45368
  // -------------------------------------------------------------------------
45181
- async render(outputDir) {
45369
+ async render(outputDir, opts = {}) {
45182
45370
  const notInit = this._notInitError();
45183
45371
  if (notInit) return notInit;
45184
- const result = await this._render.render(outputDir);
45372
+ const result = await this._render.render(outputDir, opts);
45185
45373
  for (const h6 of this._renderHandlers) h6(result);
45186
45374
  return result;
45187
45375
  }
45376
+ /**
45377
+ * Render into `outputDir` through the shared single-flight guard, intended to
45378
+ * be called fire-and-forget (e.g. the GUI's instant-open background render).
45379
+ *
45380
+ * The guard ({@link _renderGuarded}) holds {@link _autoRenderInFlight} for the
45381
+ * render's duration, so a data mutation that lands while this render is in
45382
+ * flight is deferred by {@link _runAutoRender} and coalesced — when this
45383
+ * render settles, `finally` clears the flag and re-arms exactly one follow-up
45384
+ * render via {@link _rearmAutoRenderIfPending}. Net invariant: at most one
45385
+ * render to a given dir at a time.
45386
+ *
45387
+ * Errors propagate to the caller (the GUI surfaces them, never silently swallowed); they are
45388
+ * not swallowed here.
45389
+ */
45390
+ async renderInBackground(outputDir, opts = {}) {
45391
+ const notInit = this._notInitError();
45392
+ if (notInit) return notInit;
45393
+ return this._renderGuarded(outputDir, opts);
45394
+ }
45188
45395
  async sync(outputDir) {
45189
45396
  const notInit = this._notInitError();
45190
45397
  if (notInit) return notInit;
@@ -45526,6 +45733,30 @@ var Lattice = class _Lattice {
45526
45733
  }, this._autoRenderDebounceMs);
45527
45734
  this._autoRenderTimer.unref();
45528
45735
  }
45736
+ /**
45737
+ * Shared single-flight render path used by {@link renderInBackground}.
45738
+ *
45739
+ * Holds {@link _autoRenderInFlight} for the render's duration so the
45740
+ * mutation-driven {@link _runAutoRender} defers while this render runs (it
45741
+ * sees the flag and marks itself pending instead of starting a second,
45742
+ * overlapping render). On settle, `finally` clears the flag and re-arms a
45743
+ * single coalesced follow-up render if any mutation arrived mid-flight.
45744
+ * Errors propagate to the caller; the flag is always cleared.
45745
+ */
45746
+ async _renderGuarded(outputDir, opts) {
45747
+ while (this._autoRenderInFlight) {
45748
+ await new Promise((r6) => setImmediate(r6));
45749
+ }
45750
+ this._autoRenderInFlight = true;
45751
+ try {
45752
+ const result = await this._render.render(outputDir, opts);
45753
+ for (const h6 of this._renderHandlers) h6(result);
45754
+ return result;
45755
+ } finally {
45756
+ this._autoRenderInFlight = false;
45757
+ this._rearmAutoRenderIfPending();
45758
+ }
45759
+ }
45529
45760
  async _runAutoRender() {
45530
45761
  const dir = this._autoRenderDir;
45531
45762
  if (!dir || !this._initialized) return;
@@ -47153,6 +47384,31 @@ function pkSqlExpr(pkCols, prefix) {
47153
47384
  return pkCols.map((c6) => `CAST(${prefix}"${c6}" AS TEXT)`).join(` || chr(9) || `);
47154
47385
  }
47155
47386
  var MEMBER_GROUP = "lattice_members";
47387
+ function pinDefinerSearchPath(sql, schema) {
47388
+ const safe = schema.replace(/"/g, '""');
47389
+ return sql.replace(
47390
+ /SECURITY DEFINER AS/g,
47391
+ `SECURITY DEFINER SET search_path = "${safe}", pg_temp AS`
47392
+ );
47393
+ }
47394
+ async function cloudSchema(db) {
47395
+ const row = await getAsyncOrSync(db.adapter, `SELECT current_schema() AS schema`);
47396
+ const s2 = row?.schema;
47397
+ if (typeof s2 !== "string" || s2.length === 0) {
47398
+ throw new Error("cloud RLS: could not resolve current_schema() for search_path pinning");
47399
+ }
47400
+ return s2;
47401
+ }
47402
+ function revokeSchemaCreateSql(schema) {
47403
+ const lit = `'${schema.replace(/'/g, "''")}'`;
47404
+ return `
47405
+ DO $LATTICE_REVOKE$ BEGIN
47406
+ EXECUTE format('REVOKE CREATE ON SCHEMA %I FROM PUBLIC', ${lit});
47407
+ EXCEPTION WHEN OTHERS THEN
47408
+ NULL; -- not the schema owner, or already revoked
47409
+ END $LATTICE_REVOKE$;
47410
+ `;
47411
+ }
47156
47412
  var CLOUD_RLS_BOOTSTRAP_SQL = `
47157
47413
  -- Member group (NOLOGIN). Members inherit schema/connect/table privileges from it;
47158
47414
  -- RLS filters per the individual member's login role, so the group never widens
@@ -47212,6 +47468,52 @@ CREATE TABLE IF NOT EXISTS "__lattice_cell_grants" (
47212
47468
  PRIMARY KEY ("table_name", "pk", "column_name", "grantee_role")
47213
47469
  );
47214
47470
 
47471
+ -- Per-table policy: the owner-controlled defaults that govern a whole table.
47472
+ -- default_row_visibility is the visibility NEW rows are stamped with (the insert
47473
+ -- trigger reads it); never_share is a hard exclusion \u2014 the share/grant functions
47474
+ -- refuse to elevate such a table and the trigger forces its rows private. Owner-
47475
+ -- managed; members have no grant (it never appears in their data API).
47476
+ CREATE TABLE IF NOT EXISTS "__lattice_table_policy" (
47477
+ "table_name" text PRIMARY KEY,
47478
+ "default_row_visibility" text NOT NULL DEFAULT 'private'
47479
+ CHECK ("default_row_visibility" IN ('private','everyone')),
47480
+ "never_share" boolean NOT NULL DEFAULT false,
47481
+ "updated_by" text NOT NULL DEFAULT session_user,
47482
+ "updated_at" timestamptz NOT NULL DEFAULT now()
47483
+ );
47484
+
47485
+ -- Per-column audience policy: the CANONICAL store of which column carries which
47486
+ -- audience spec (role: / subject: / source: / owner / everyone). Previously the
47487
+ -- spec lived only in the owner's on-disk YAML and was compiled into the mask view
47488
+ -- once at init; storing it here makes it cloud-canonical and member-consistent.
47489
+ -- The generated <table>_v mask view is regenerated from THIS table on change.
47490
+ -- Owner-managed; members have no grant.
47491
+ CREATE TABLE IF NOT EXISTS "__lattice_column_policy" (
47492
+ "table_name" text NOT NULL,
47493
+ "column_name" text NOT NULL,
47494
+ "audience" text NOT NULL,
47495
+ "updated_by" text NOT NULL DEFAULT session_user,
47496
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
47497
+ PRIMARY KEY ("table_name", "column_name")
47498
+ );
47499
+
47500
+ -- Owner-only audit of issued member invites: which scoped role was minted for
47501
+ -- which email (HASHED \u2014 the plaintext email is never stored), when it expires,
47502
+ -- and whether it was redeemed/revoked. No plaintext password is ever stored
47503
+ -- (the credential lives only inside the email-bound token the owner delivers).
47504
+ -- Owner-managed; members have no grant. Named distinctly from any legacy
47505
+ -- team-model invitations table so a pre-existing cloud never collides.
47506
+ CREATE TABLE IF NOT EXISTS "__lattice_member_invites" (
47507
+ "id" text PRIMARY KEY,
47508
+ "role" text NOT NULL,
47509
+ "email_hash" text NOT NULL,
47510
+ "created_by" text NOT NULL DEFAULT session_user,
47511
+ "created_at" timestamptz NOT NULL DEFAULT now(),
47512
+ "expires_at" timestamptz NOT NULL,
47513
+ "redeemed_at" timestamptz,
47514
+ "revoked_at" timestamptz
47515
+ );
47516
+
47215
47517
  -- Visibility check. SECURITY DEFINER so it reads bookkeeping the member can't;
47216
47518
  -- keyed on session_user (the member's login role). A row with no ownership record
47217
47519
  -- is visible to nobody.
@@ -47237,6 +47539,10 @@ BEGIN
47237
47539
  IF p_visibility NOT IN ('private','everyone','custom') THEN
47238
47540
  RAISE EXCEPTION 'lattice: invalid visibility %', p_visibility;
47239
47541
  END IF;
47542
+ IF p_visibility <> 'private'
47543
+ AND COALESCE((SELECT "never_share" FROM "__lattice_table_policy" WHERE "table_name" = p_table), false) THEN
47544
+ RAISE EXCEPTION 'lattice: "%" is a private-only table and cannot be shared', p_table;
47545
+ END IF;
47240
47546
  SELECT o."owner_role" INTO v_owner FROM "__lattice_owners" o
47241
47547
  WHERE o."table_name" = p_table AND o."pk" = p_pk;
47242
47548
  IF v_owner IS NULL THEN RAISE EXCEPTION 'lattice: no ownership record for %/%', p_table, p_pk; END IF;
@@ -47250,6 +47556,9 @@ CREATE OR REPLACE FUNCTION lattice_grant_row(p_table text, p_pk text, p_grantee
47250
47556
  RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $fn$
47251
47557
  DECLARE v_owner text;
47252
47558
  BEGIN
47559
+ IF COALESCE((SELECT "never_share" FROM "__lattice_table_policy" WHERE "table_name" = p_table), false) THEN
47560
+ RAISE EXCEPTION 'lattice: "%" is a private-only table and cannot be shared', p_table;
47561
+ END IF;
47253
47562
  SELECT o."owner_role" INTO v_owner FROM "__lattice_owners" o
47254
47563
  WHERE o."table_name" = p_table AND o."pk" = p_pk;
47255
47564
  IF v_owner IS NULL THEN RAISE EXCEPTION 'lattice: no ownership record for %/%', p_table, p_pk; END IF;
@@ -47339,6 +47648,9 @@ CREATE OR REPLACE FUNCTION lattice_grant_cell(p_table text, p_pk text, p_column
47339
47648
  RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $fn$
47340
47649
  DECLARE v_owner text;
47341
47650
  BEGIN
47651
+ IF COALESCE((SELECT "never_share" FROM "__lattice_table_policy" WHERE "table_name" = p_table), false) THEN
47652
+ RAISE EXCEPTION 'lattice: "%" is a private-only table and cannot be shared', p_table;
47653
+ END IF;
47342
47654
  SELECT o."owner_role" INTO v_owner FROM "__lattice_owners" o
47343
47655
  WHERE o."table_name" = p_table AND o."pk" = p_pk;
47344
47656
  IF v_owner IS NULL OR v_owner <> session_user THEN
@@ -47371,6 +47683,87 @@ RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER AS $fn$
47371
47683
  SELECT lattice_row_visible('files', p_source_ref)
47372
47684
  $fn$;
47373
47685
 
47686
+ -- Is the connected member the OWNER of this row? Used by the "owner" column
47687
+ -- audience (a secret column reveals only to the row owner). SECURITY DEFINER +
47688
+ -- session_user, like the other predicates.
47689
+ CREATE OR REPLACE FUNCTION lattice_is_owner(p_table text, p_pk text)
47690
+ RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER AS $fn$
47691
+ SELECT EXISTS (
47692
+ SELECT 1 FROM "__lattice_owners" o
47693
+ WHERE o."table_name" = p_table AND o."pk" = p_pk AND o."owner_role" = session_user
47694
+ )
47695
+ $fn$;
47696
+
47697
+ -- Owner-only: set a table's default row visibility for NEW rows. Raises unless the
47698
+ -- caller can create roles (a cloud owner / DBA), like lattice_assign_role. Rejects
47699
+ -- 'everyone' on a never-share table.
47700
+ CREATE OR REPLACE FUNCTION lattice_set_table_default_visibility(p_table text, p_visibility text)
47701
+ RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $fn$
47702
+ BEGIN
47703
+ IF NOT (SELECT rolcreaterole FROM pg_roles WHERE rolname = session_user) THEN
47704
+ RAISE EXCEPTION 'lattice: only a cloud owner may set a table''s default visibility';
47705
+ END IF;
47706
+ IF p_visibility NOT IN ('private','everyone') THEN
47707
+ RAISE EXCEPTION 'lattice: invalid default visibility %', p_visibility;
47708
+ END IF;
47709
+ IF p_visibility = 'everyone'
47710
+ AND COALESCE((SELECT "never_share" FROM "__lattice_table_policy" WHERE "table_name" = p_table), false) THEN
47711
+ RAISE EXCEPTION 'lattice: "%" is a private-only table; its rows cannot default to everyone', p_table;
47712
+ END IF;
47713
+ INSERT INTO "__lattice_table_policy" ("table_name","default_row_visibility","updated_by","updated_at")
47714
+ VALUES (p_table, p_visibility, session_user, now())
47715
+ ON CONFLICT ("table_name") DO UPDATE
47716
+ SET "default_row_visibility" = EXCLUDED."default_row_visibility",
47717
+ "updated_by" = session_user, "updated_at" = now();
47718
+ END $fn$;
47719
+
47720
+ -- Owner-only: mark a table never-shareable (Secrets/Messages-class). When true the
47721
+ -- share/grant functions raise and the insert trigger forces new rows private; the
47722
+ -- default visibility is also forced private. Turning it ON also RETROACTIVELY
47723
+ -- privatizes the table: any row currently shared ('everyone'/'custom') is reset to
47724
+ -- 'private' and every existing row/cell grant on the table is dropped \u2014 otherwise
47725
+ -- flagging a table never-share would leave already-leaked rows visible, defeating
47726
+ -- the point. Idempotent: re-running with already-private rows updates nothing.
47727
+ CREATE OR REPLACE FUNCTION lattice_set_table_never_share(p_table text, p_on boolean)
47728
+ RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $fn$
47729
+ BEGIN
47730
+ IF NOT (SELECT rolcreaterole FROM pg_roles WHERE rolname = session_user) THEN
47731
+ RAISE EXCEPTION 'lattice: only a cloud owner may change a table''s never-share flag';
47732
+ END IF;
47733
+ INSERT INTO "__lattice_table_policy" ("table_name","never_share","default_row_visibility","updated_by","updated_at")
47734
+ VALUES (p_table, p_on, CASE WHEN p_on THEN 'private' ELSE 'private' END, session_user, now())
47735
+ ON CONFLICT ("table_name") DO UPDATE
47736
+ SET "never_share" = EXCLUDED."never_share",
47737
+ "default_row_visibility" = CASE WHEN EXCLUDED."never_share"
47738
+ THEN 'private' ELSE "__lattice_table_policy"."default_row_visibility" END,
47739
+ "updated_by" = session_user, "updated_at" = now();
47740
+ IF p_on THEN
47741
+ UPDATE "__lattice_owners" SET "visibility" = 'private', "updated_at" = now()
47742
+ WHERE "table_name" = p_table AND "visibility" <> 'private';
47743
+ DELETE FROM "__lattice_row_grants" WHERE "table_name" = p_table;
47744
+ DELETE FROM "__lattice_cell_grants" WHERE "table_name" = p_table;
47745
+ END IF;
47746
+ END $fn$;
47747
+
47748
+ -- Owner-only: set (or clear) a column's audience spec in the canonical DB store.
47749
+ -- An empty/null spec removes the policy row (column becomes unmasked). The GUI/lib
47750
+ -- regenerates the table's mask view from this store after calling this.
47751
+ CREATE OR REPLACE FUNCTION lattice_set_column_audience(p_table text, p_column text, p_audience text)
47752
+ RETURNS void LANGUAGE plpgsql SECURITY DEFINER AS $fn$
47753
+ BEGIN
47754
+ IF NOT (SELECT rolcreaterole FROM pg_roles WHERE rolname = session_user) THEN
47755
+ RAISE EXCEPTION 'lattice: only a cloud owner may set a column audience';
47756
+ END IF;
47757
+ IF p_audience IS NULL OR btrim(p_audience) = '' THEN
47758
+ DELETE FROM "__lattice_column_policy" WHERE "table_name" = p_table AND "column_name" = p_column;
47759
+ ELSE
47760
+ INSERT INTO "__lattice_column_policy" ("table_name","column_name","audience","updated_by","updated_at")
47761
+ VALUES (p_table, p_column, p_audience, session_user, now())
47762
+ ON CONFLICT ("table_name","column_name") DO UPDATE
47763
+ SET "audience" = EXCLUDED."audience", "updated_by" = session_user, "updated_at" = now();
47764
+ END IF;
47765
+ END $fn$;
47766
+
47374
47767
  -- Append-only change feed. The per-table ownership trigger records one row per
47375
47768
  -- INSERT/UPDATE/DELETE; the AFTER INSERT trigger here fires pg_notify so a
47376
47769
  -- connected member's realtime broker refreshes. Members get no direct access \u2014
@@ -47415,7 +47808,20 @@ CREATE OR REPLACE FUNCTION "${trg}"() RETURNS trigger LANGUAGE plpgsql SECURITY
47415
47808
  BEGIN
47416
47809
  IF TG_OP = 'INSERT' THEN
47417
47810
  INSERT INTO "__lattice_owners" ("table_name","pk","owner_role","visibility")
47418
- VALUES (${lit}, ${pkNew}, session_user, 'private')
47811
+ VALUES (${lit}, ${pkNew}, session_user,
47812
+ CASE
47813
+ -- never-share always wins: such a table's rows are private, full stop.
47814
+ WHEN COALESCE((SELECT "never_share" FROM "__lattice_table_policy" WHERE "table_name" = ${lit}), false)
47815
+ THEN 'private'
47816
+ -- per-INSERT override: a caller forcing visibility for THIS write (e.g.
47817
+ -- chat "private mode") sets the transaction-local lattice.force_row_visibility
47818
+ -- GUC, so the row is stamped atomically at insert \u2014 never momentarily at
47819
+ -- the table default, and the change-feed NOTIFY (deferred to COMMIT) only
47820
+ -- fires once the row already carries this visibility.
47821
+ WHEN NULLIF(current_setting('lattice.force_row_visibility', true), '') IN ('private','everyone')
47822
+ THEN current_setting('lattice.force_row_visibility', true)
47823
+ ELSE COALESCE((SELECT "default_row_visibility" FROM "__lattice_table_policy" WHERE "table_name" = ${lit}), 'private')
47824
+ END)
47419
47825
  ON CONFLICT ("table_name","pk") DO NOTHING;
47420
47826
  INSERT INTO "__lattice_changes" ("table_name","pk","op","owner_role")
47421
47827
  VALUES (${lit}, ${pkNew}, 'upsert', session_user);
@@ -47455,19 +47861,28 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
47455
47861
  }
47456
47862
  async function installCloudRls(db) {
47457
47863
  if (!isPg(db)) return;
47864
+ const schema = await cloudSchema(db);
47458
47865
  const migration = {
47459
47866
  // v3 added the audience helpers; v4 the role model; v5 the per-card override
47460
- // model (__lattice_cell_grants + lattice_cell_visible / lattice_grant_cell).
47461
- // The bootstrap is fully idempotent (CREATE OR REPLACE / IF NOT EXISTS).
47462
- version: "internal:cloud-rls:bootstrap:v5",
47463
- sql: CLOUD_RLS_BOOTSTRAP_SQL
47867
+ // model (__lattice_cell_grants + lattice_cell_visible / lattice_grant_cell);
47868
+ // v6 added per-table policy (__lattice_table_policy: default_row_visibility +
47869
+ // never_share, enforced in the insert trigger + share/grant guards), the
47870
+ // canonical column-audience store (__lattice_column_policy), lattice_is_owner,
47871
+ // and the owner-only setters; v7 pins search_path on every SECURITY DEFINER
47872
+ // helper (closes the pg_temp-shadow RLS bypass) + revokes schema CREATE from
47873
+ // PUBLIC. The bootstrap is fully idempotent.
47874
+ version: "internal:cloud-rls:bootstrap:v7",
47875
+ sql: pinDefinerSearchPath(CLOUD_RLS_BOOTSTRAP_SQL, schema) + revokeSchemaCreateSql(schema)
47464
47876
  };
47465
47877
  await db.migrate([migration]);
47466
47878
  }
47467
47879
  async function enableChangelogRls(db) {
47468
47880
  if (!isPg(db)) return;
47469
47881
  const migration = {
47470
- version: "internal:cloud-rls:changelog:v1",
47882
+ // v2: ground-truth/audit entries are owner-only (was lattice_row_visible),
47883
+ // closing the masked-column-via-history leak. Bump re-installs the policy on
47884
+ // existing clouds.
47885
+ version: "internal:cloud-rls:changelog:v2",
47471
47886
  sql: `
47472
47887
  ALTER TABLE "__lattice_changelog" ENABLE ROW LEVEL SECURITY;
47473
47888
  ALTER TABLE "__lattice_changelog" FORCE ROW LEVEL SECURITY;
@@ -47482,7 +47897,7 @@ CREATE POLICY "lattice_changelog_sel" ON "__lattice_changelog" FOR SELECT USING
47482
47897
  SELECT 1 FROM jsonb_array_elements_text("source_ref"::jsonb) AS src(sid)
47483
47898
  WHERE NOT lattice_source_visible(src.sid)
47484
47899
  )
47485
- ELSE lattice_row_visible("table_name", "row_id")
47900
+ ELSE lattice_is_owner("table_name", "row_id")
47486
47901
  END
47487
47902
  );
47488
47903
  DROP POLICY IF EXISTS "lattice_changelog_ins" ON "__lattice_changelog";
@@ -47493,9 +47908,10 @@ CREATE POLICY "lattice_changelog_ins" ON "__lattice_changelog" FOR INSERT WITH C
47493
47908
  }
47494
47909
  async function enableRlsForTable(db, table, pkCols) {
47495
47910
  if (!isPg(db)) return;
47911
+ const schema = await cloudSchema(db);
47496
47912
  const migration = {
47497
- version: `internal:cloud-rls:table:${table}:v2`,
47498
- sql: tableRlsSql(table, pkCols)
47913
+ version: `internal:cloud-rls:table:${table}:v3`,
47914
+ sql: pinDefinerSearchPath(tableRlsSql(table, pkCols), schema)
47499
47915
  };
47500
47916
  await db.migrate([migration]);
47501
47917
  }
@@ -47625,12 +48041,17 @@ function isRowAudience(audience) {
47625
48041
  const a6 = (audience ?? "").trim();
47626
48042
  return a6 === "" || a6 === "everyone" || a6 === "row-audience";
47627
48043
  }
47628
- function audiencePredicate(audience) {
48044
+ function audiencePredicate(audience, ctx) {
47629
48045
  if (isRowAudience(audience)) return "true";
47630
48046
  const clauses = audience.split("+").map((c6) => c6.trim()).filter(Boolean);
47631
48047
  const parts = [];
47632
48048
  for (const clause of clauses) {
47633
48049
  if (clause === "everyone" || clause === "row-audience") return "true";
48050
+ if (clause === "owner") {
48051
+ if (!ctx) throw new Error('lattice: the "owner" audience needs a row context');
48052
+ parts.push(`lattice_is_owner(${ctx.tableLit}, ${ctx.pkExpr})`);
48053
+ continue;
48054
+ }
47634
48055
  const idx = clause.indexOf(":");
47635
48056
  const kind = idx === -1 ? clause : clause.slice(0, idx);
47636
48057
  const arg = idx === -1 ? "" : clause.slice(idx + 1).trim();
@@ -47665,7 +48086,7 @@ function audienceViewSql(table, columns, pkCols, columnAudience) {
47665
48086
  const selectCols = columns.map((col) => {
47666
48087
  const aud = columnAudience[col] ?? "";
47667
48088
  if (isRowAudience(aud)) return quoteIdent(col);
47668
- const pred = audiencePredicate(aud);
48089
+ const pred = audiencePredicate(aud, { tableLit: lit, pkExpr });
47669
48090
  if (pred === "true") return quoteIdent(col);
47670
48091
  const colLit = `'${col.replace(/'/g, "''")}'`;
47671
48092
  const full = `(${pred}) OR lattice_cell_visible(${lit}, ${pkExpr}, ${colLit})`;
@@ -47700,6 +48121,92 @@ async function enableAudienceView(db, table, columns, pkCols, columnAudience) {
47700
48121
  };
47701
48122
  await db.migrate([migration]);
47702
48123
  }
48124
+ async function loadColumnPolicy(db, table) {
48125
+ if (db.getDialect() !== "postgres") return {};
48126
+ const rows = await allAsyncOrSync(
48127
+ db.adapter,
48128
+ `SELECT "column_name", "audience" FROM "__lattice_column_policy" WHERE "table_name" = ?`,
48129
+ [table]
48130
+ );
48131
+ const out = {};
48132
+ for (const r6 of rows) out[r6.column_name] = r6.audience;
48133
+ return out;
48134
+ }
48135
+ async function seedColumnPolicyFromYaml(db, table, yamlAudience) {
48136
+ if (db.getDialect() !== "postgres") return;
48137
+ const marker = `internal:cloud-column-seed:${table}:v1`;
48138
+ const already = await getAsyncOrSync(
48139
+ db.adapter,
48140
+ `SELECT 1 AS one FROM "__lattice_migrations" WHERE "version" = ?`,
48141
+ [marker]
48142
+ );
48143
+ if (already) return;
48144
+ for (const [col, aud] of Object.entries(yamlAudience)) {
48145
+ if (isRowAudience(aud)) continue;
48146
+ await runAsyncOrSync(
48147
+ db.adapter,
48148
+ `INSERT INTO "__lattice_column_policy" ("table_name","column_name","audience")
48149
+ VALUES (?, ?, ?) ON CONFLICT ("table_name","column_name") DO NOTHING`,
48150
+ [table, col, aud]
48151
+ );
48152
+ }
48153
+ await runAsyncOrSync(
48154
+ db.adapter,
48155
+ `INSERT INTO "__lattice_migrations" ("version","applied_at") VALUES (?, ?)
48156
+ ON CONFLICT ("version") DO NOTHING`,
48157
+ [marker, (/* @__PURE__ */ new Date()).toISOString()]
48158
+ );
48159
+ }
48160
+ async function regenerateAudienceViewFromDb(db, table, columns, pkCols) {
48161
+ if (db.getDialect() !== "postgres") return;
48162
+ if (pkCols.length === 0) return;
48163
+ const spec = await loadColumnPolicy(db, table);
48164
+ const view = quoteIdent(`${table}_v`);
48165
+ const base = quoteIdent(table);
48166
+ if (!tableNeedsAudienceView(spec)) {
48167
+ await runAsyncOrSync(
48168
+ db.adapter,
48169
+ `DROP VIEW IF EXISTS ${view};
48170
+ GRANT SELECT ON ${base} TO ${MEMBER_GROUP};`
48171
+ );
48172
+ return;
48173
+ }
48174
+ await runAsyncOrSync(db.adapter, audienceViewSql(table, columns, pkCols, spec));
48175
+ }
48176
+ async function setColumnAudience(db, table, column, audience, columns, pkCols) {
48177
+ if (db.getDialect() !== "postgres") return;
48178
+ await runAsyncOrSync(db.adapter, `SELECT lattice_set_column_audience(?, ?, ?)`, [
48179
+ table,
48180
+ column,
48181
+ audience
48182
+ ]);
48183
+ await regenerateAudienceViewFromDb(db, table, columns, pkCols);
48184
+ }
48185
+
48186
+ // src/cloud/table-policy.ts
48187
+ async function getTablePolicy(db, table) {
48188
+ if (db.getDialect() !== "postgres") return { defaultRowVisibility: "private", neverShare: false };
48189
+ const row = await getAsyncOrSync(
48190
+ db.adapter,
48191
+ `SELECT "default_row_visibility", "never_share" FROM "__lattice_table_policy" WHERE "table_name" = ?`,
48192
+ [table]
48193
+ );
48194
+ return {
48195
+ defaultRowVisibility: row?.default_row_visibility === "everyone" ? "everyone" : "private",
48196
+ neverShare: row?.never_share === true
48197
+ };
48198
+ }
48199
+ async function setTableDefaultVisibility(db, table, visibility) {
48200
+ if (db.getDialect() !== "postgres") return;
48201
+ await runAsyncOrSync(db.adapter, `SELECT lattice_set_table_default_visibility(?, ?)`, [
48202
+ table,
48203
+ visibility
48204
+ ]);
48205
+ }
48206
+ async function setTableNeverShare(db, table, on) {
48207
+ if (db.getDialect() !== "postgres") return;
48208
+ await runAsyncOrSync(db.adapter, `SELECT lattice_set_table_never_share(?, ?)`, [table, on]);
48209
+ }
47703
48210
 
47704
48211
  // src/cloud/fold-cache.ts
47705
48212
  function viewerSignature(viewer) {
@@ -47774,9 +48281,12 @@ END $fn$;
47774
48281
  `;
47775
48282
  async function installCloudSettings(db) {
47776
48283
  if (db.getDialect() !== "postgres") return;
48284
+ const schema = await cloudSchema(db);
47777
48285
  const migration = {
47778
- version: "internal:cloud-settings:v1",
47779
- sql: CLOUD_SETTINGS_BOOTSTRAP_SQL
48286
+ // v2 pins search_path on the two SECURITY DEFINER helpers (closes the
48287
+ // pg_temp-shadow class of bypass on the settings getter/setter).
48288
+ version: "internal:cloud-settings:v2",
48289
+ sql: pinDefinerSearchPath(CLOUD_SETTINGS_BOOTSTRAP_SQL, schema)
47780
48290
  };
47781
48291
  await db.migrate([migration]);
47782
48292
  }
@@ -47803,7 +48313,8 @@ async function secureCloud(db) {
47803
48313
  await installCloudSettings(db);
47804
48314
  await db.ensureObservationSubstrate();
47805
48315
  await enableChangelogRls(db);
47806
- for (const table of db.getRegisteredTableNames()) {
48316
+ const registered = db.getRegisteredTableNames();
48317
+ for (const table of registered) {
47807
48318
  if (table.startsWith("__lattice_") || table.startsWith("_lattice_")) continue;
47808
48319
  const pk = db.getPrimaryKey(table);
47809
48320
  if (pk.length === 0) continue;
@@ -47811,9 +48322,21 @@ async function secureCloud(db) {
47811
48322
  await enableRlsForTable(db, table, pk);
47812
48323
  const cols = db.getRegisteredColumns(table);
47813
48324
  if (cols) {
47814
- await enableAudienceView(db, table, Object.keys(cols), pk, db.getColumnAudience(table));
48325
+ await seedColumnPolicyFromYaml(db, table, db.getColumnAudience(table));
48326
+ await regenerateAudienceViewFromDb(db, table, Object.keys(cols), pk);
47815
48327
  }
47816
48328
  }
48329
+ if (registered.includes("secrets")) {
48330
+ await runAsyncOrSync(db.adapter, `SELECT lattice_set_table_never_share('secrets', true)`);
48331
+ }
48332
+ await runAsyncOrSync(
48333
+ db.adapter,
48334
+ `DO $LATTICE$ BEGIN
48335
+ IF to_regclass('__lattice_user_identity') IS NOT NULL THEN
48336
+ EXECUTE 'GRANT SELECT, INSERT, UPDATE ON "__lattice_user_identity" TO ${MEMBER_GROUP}';
48337
+ END IF;
48338
+ END $LATTICE$`
48339
+ );
47817
48340
  }
47818
48341
 
47819
48342
  // src/ai/llm-client.ts
@@ -48410,6 +48933,7 @@ function defaultPdfSender(auth) {
48410
48933
  NATIVE_ENTITY_NAMES,
48411
48934
  NATIVE_REGISTRY_TABLE,
48412
48935
  PostgresAdapter,
48936
+ ProgressThrottle,
48413
48937
  READ_ONLY_HEADER,
48414
48938
  ROOT_DIRNAME,
48415
48939
  ReferenceUnavailableError,
@@ -48473,6 +48997,7 @@ function defaultPdfSender(auth) {
48473
48997
  getCloudSetting,
48474
48998
  getDbCredential,
48475
48999
  getOrCreateMasterKey,
49000
+ getTablePolicy,
48476
49001
  getWorkspace,
48477
49002
  grantCell,
48478
49003
  hasFtsIndex,
@@ -48490,6 +49015,7 @@ function defaultPdfSender(auth) {
48490
49015
  listNativeBindings,
48491
49016
  listTokens,
48492
49017
  listWorkspaces,
49018
+ loadColumnPolicy,
48493
49019
  manifestPath,
48494
49020
  markdownTable,
48495
49021
  memberRoleName,
@@ -48517,6 +49043,7 @@ function defaultPdfSender(auth) {
48517
49043
  readToken,
48518
49044
  referenceLocalFile,
48519
49045
  referenceUrl,
49046
+ regenerateAudienceViewFromDb,
48520
49047
  registerNativeEntities,
48521
49048
  registryPath,
48522
49049
  resolveActiveS3Config,
@@ -48531,9 +49058,13 @@ function defaultPdfSender(auth) {
48531
49058
  saveDbCredentialForTeam,
48532
49059
  sealUnderSource,
48533
49060
  secureCloud,
49061
+ seedColumnPolicyFromYaml,
48534
49062
  setActiveWorkspace,
48535
49063
  setCloudSetting,
49064
+ setColumnAudience,
48536
49065
  setRowVisibility,
49066
+ setTableDefaultVisibility,
49067
+ setTableNeverShare,
48537
49068
  shredSource,
48538
49069
  slugify,
48539
49070
  summarizeText,