@happyvertical/smrt-core 0.57.45 → 0.57.47

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 (189) hide show
  1. package/AGENTS.md +11 -3
  2. package/agents/browser-entry.md +16 -0
  3. package/agents/build-knowledge.md +58 -14
  4. package/agents/change-feed.md +20 -0
  5. package/agents/generators.md +69 -3
  6. package/agents/schema-paths.md +82 -0
  7. package/dist/_virtual/_rolldown/runtime.js +22 -1
  8. package/dist/browser.d.ts +2 -2
  9. package/dist/browser.d.ts.map +1 -1
  10. package/dist/browser.js +2 -2
  11. package/dist/cascade.d.ts +46 -0
  12. package/dist/cascade.d.ts.map +1 -1
  13. package/dist/cascade.js +96 -7
  14. package/dist/cascade.js.map +1 -1
  15. package/dist/change-feed.d.ts.map +1 -1
  16. package/dist/change-feed.js +7 -4
  17. package/dist/change-feed.js.map +1 -1
  18. package/dist/class.d.ts.map +1 -1
  19. package/dist/class.js +3 -3
  20. package/dist/class.js.map +1 -1
  21. package/dist/consumer-plugin/index.d.ts +13 -0
  22. package/dist/consumer-plugin/index.d.ts.map +1 -1
  23. package/dist/consumer-plugin/index.js +69 -13
  24. package/dist/consumer-plugin/index.js.map +1 -1
  25. package/dist/consumer-plugin.js +2 -2
  26. package/dist/database.d.ts.map +1 -1
  27. package/dist/database.js +3 -3
  28. package/dist/database.js.map +1 -1
  29. package/dist/dispatch/bus.d.ts.map +1 -1
  30. package/dist/dispatch/bus.js +1 -4
  31. package/dist/dispatch/bus.js.map +1 -1
  32. package/dist/dispatch/index.d.ts +1 -1
  33. package/dist/dispatch/index.d.ts.map +1 -1
  34. package/dist/dispatch/types.d.ts +22 -3
  35. package/dist/dispatch/types.d.ts.map +1 -1
  36. package/dist/generators/changes-route.d.ts.map +1 -1
  37. package/dist/generators/changes-route.js +2 -2
  38. package/dist/generators/changes-route.js.map +1 -1
  39. package/dist/generators/custom-action.d.ts +11 -0
  40. package/dist/generators/custom-action.d.ts.map +1 -1
  41. package/dist/generators/custom-action.js +23 -4
  42. package/dist/generators/custom-action.js.map +1 -1
  43. package/dist/index.d.ts +2 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +3 -2
  46. package/dist/lazy-config.d.ts +120 -3
  47. package/dist/lazy-config.d.ts.map +1 -1
  48. package/dist/lazy-config.js +81 -10
  49. package/dist/lazy-config.js.map +1 -1
  50. package/dist/manifest/generator.d.ts +8 -2
  51. package/dist/manifest/generator.d.ts.map +1 -1
  52. package/dist/manifest/generator.js +6 -5
  53. package/dist/manifest/generator.js.map +1 -1
  54. package/dist/manifest/index.d.ts +1 -0
  55. package/dist/manifest/index.d.ts.map +1 -1
  56. package/dist/manifest/index.js +2 -1
  57. package/dist/manifest/index.js.map +1 -1
  58. package/dist/manifest/local-manifest-reader.d.ts +13 -2
  59. package/dist/manifest/local-manifest-reader.d.ts.map +1 -1
  60. package/dist/manifest/local-manifest-reader.js +38 -6
  61. package/dist/manifest/local-manifest-reader.js.map +1 -1
  62. package/dist/manifest/manifest-loader.d.ts +9 -8
  63. package/dist/manifest/manifest-loader.d.ts.map +1 -1
  64. package/dist/manifest/manifest-loader.js +67 -27
  65. package/dist/manifest/manifest-loader.js.map +1 -1
  66. package/dist/manifest/object-declarations.d.ts +116 -0
  67. package/dist/manifest/object-declarations.d.ts.map +1 -0
  68. package/dist/manifest/object-declarations.js +161 -0
  69. package/dist/manifest/object-declarations.js.map +1 -0
  70. package/dist/manifest/static-manifest.d.ts.map +1 -1
  71. package/dist/manifest/static-manifest.js +5 -3
  72. package/dist/manifest/static-manifest.js.map +1 -1
  73. package/dist/manifest/store.d.ts.map +1 -1
  74. package/dist/manifest/store.js +18 -2
  75. package/dist/manifest/store.js.map +1 -1
  76. package/dist/manifest/test-artifacts.d.ts +57 -0
  77. package/dist/manifest/test-artifacts.d.ts.map +1 -0
  78. package/dist/manifest/test-artifacts.js +111 -0
  79. package/dist/manifest/test-artifacts.js.map +1 -0
  80. package/dist/manifest.js +2 -1
  81. package/dist/manifest.json +5 -3
  82. package/dist/migrations/differ.d.ts.map +1 -1
  83. package/dist/migrations/differ.js +11 -3
  84. package/dist/migrations/differ.js.map +1 -1
  85. package/dist/migrations/index.d.ts +2 -0
  86. package/dist/migrations/index.d.ts.map +1 -1
  87. package/dist/migrations/index.js +3 -1
  88. package/dist/migrations/qualified-name-aliases.d.ts.map +1 -1
  89. package/dist/migrations/qualified-name-aliases.js +1 -1
  90. package/dist/migrations/qualified-name-aliases.js.map +1 -1
  91. package/dist/migrations.js +3 -1
  92. package/dist/object.d.ts.map +1 -1
  93. package/dist/object.js +2 -2
  94. package/dist/object.js.map +1 -1
  95. package/dist/postgres-permissions.d.ts.map +1 -1
  96. package/dist/postgres-permissions.js +4 -3
  97. package/dist/postgres-permissions.js.map +1 -1
  98. package/dist/postgres-timeouts.d.ts +30 -3
  99. package/dist/postgres-timeouts.d.ts.map +1 -1
  100. package/dist/postgres-timeouts.js +39 -2
  101. package/dist/postgres-timeouts.js.map +1 -1
  102. package/dist/prebuild/index.d.ts.map +1 -1
  103. package/dist/prebuild/index.js +14 -11
  104. package/dist/prebuild/index.js.map +1 -1
  105. package/dist/registry/class-registration.d.ts.map +1 -1
  106. package/dist/registry/class-registration.js +24 -2
  107. package/dist/registry/class-registration.js.map +1 -1
  108. package/dist/registry/collection-exposure.d.ts +27 -0
  109. package/dist/registry/collection-exposure.d.ts.map +1 -1
  110. package/dist/registry/collection-exposure.js +74 -1
  111. package/dist/registry/collection-exposure.js.map +1 -1
  112. package/dist/registry/collection-resolution.d.ts.map +1 -1
  113. package/dist/registry/collection-resolution.js +1 -0
  114. package/dist/registry/collection-resolution.js.map +1 -1
  115. package/dist/registry/framework-base-classes.d.ts.map +1 -1
  116. package/dist/registry/framework-base-classes.js +5 -0
  117. package/dist/registry/framework-base-classes.js.map +1 -1
  118. package/dist/registry/schema-builder.d.ts +8 -1
  119. package/dist/registry/schema-builder.d.ts.map +1 -1
  120. package/dist/registry/schema-builder.js +17 -4
  121. package/dist/registry/schema-builder.js.map +1 -1
  122. package/dist/registry/types.d.ts +12 -0
  123. package/dist/registry/types.d.ts.map +1 -1
  124. package/dist/registry.d.ts +2 -1
  125. package/dist/registry.d.ts.map +1 -1
  126. package/dist/registry.js +30 -10
  127. package/dist/registry.js.map +1 -1
  128. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  129. package/dist/scanner/manifest-generator.js +10 -8
  130. package/dist/scanner/manifest-generator.js.map +1 -1
  131. package/dist/scanner/types.d.ts +29 -0
  132. package/dist/scanner/types.d.ts.map +1 -1
  133. package/dist/scanner/types.js.map +1 -1
  134. package/dist/schema/generator.d.ts +32 -0
  135. package/dist/schema/generator.d.ts.map +1 -1
  136. package/dist/schema/generator.js +59 -0
  137. package/dist/schema/generator.js.map +1 -1
  138. package/dist/schema/live-parity.d.ts.map +1 -1
  139. package/dist/schema/live-parity.js +4 -2
  140. package/dist/schema/live-parity.js.map +1 -1
  141. package/dist/scripts/browser-unbundled-dev.mjs +90 -0
  142. package/dist/scripts/generate-test-manifest.js +9 -49
  143. package/dist/smrt-knowledge.json +16 -16
  144. package/dist/system/compatibility.d.ts.map +1 -1
  145. package/dist/system/compatibility.js +12 -4
  146. package/dist/system/compatibility.js.map +1 -1
  147. package/dist/system/schema.d.ts +70 -8
  148. package/dist/system/schema.d.ts.map +1 -1
  149. package/dist/system/schema.js +231 -24
  150. package/dist/system/schema.js.map +1 -1
  151. package/dist/testing/database.d.ts.map +1 -1
  152. package/dist/testing/database.js +14 -2
  153. package/dist/testing/database.js.map +1 -1
  154. package/dist/tools/tool-generator.d.ts.map +1 -1
  155. package/dist/tools/tool-generator.js +2 -2
  156. package/dist/tools/tool-generator.js.map +1 -1
  157. package/dist/utils/import-workspace-module.d.ts.map +1 -1
  158. package/dist/utils/import-workspace-module.js +23 -0
  159. package/dist/utils/import-workspace-module.js.map +1 -1
  160. package/dist/utils/naming.js +5 -3
  161. package/dist/utils/naming.js.map +1 -1
  162. package/dist/vendor/pluralize/pluralize.js +421 -0
  163. package/dist/vendor/pluralize/pluralize.js.map +1 -0
  164. package/dist/vite-plugin/api-client-entries.d.ts +5 -1
  165. package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
  166. package/dist/vite-plugin/api-client-entries.js +11 -6
  167. package/dist/vite-plugin/api-client-entries.js.map +1 -1
  168. package/dist/vite-plugin/consumer-import.d.ts +28 -0
  169. package/dist/vite-plugin/consumer-import.d.ts.map +1 -0
  170. package/dist/vite-plugin/consumer-import.js +42 -0
  171. package/dist/vite-plugin/consumer-import.js.map +1 -0
  172. package/dist/vite-plugin/dev-plane-route.d.ts +6 -1
  173. package/dist/vite-plugin/dev-plane-route.d.ts.map +1 -1
  174. package/dist/vite-plugin/dev-plane-route.js +8 -9
  175. package/dist/vite-plugin/dev-plane-route.js.map +1 -1
  176. package/dist/vite-plugin/index.d.ts.map +1 -1
  177. package/dist/vite-plugin/index.js +12 -9
  178. package/dist/vite-plugin/index.js.map +1 -1
  179. package/dist/vite-plugin/resources-route.d.ts.map +1 -1
  180. package/dist/vite-plugin/resources-route.js +3 -8
  181. package/dist/vite-plugin/resources-route.js.map +1 -1
  182. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  183. package/dist/vite-plugin/sveltekit-generator.js +7 -11
  184. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  185. package/dist/vite-plugin/sync-apply-route.d.ts +16 -0
  186. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  187. package/dist/vite-plugin/sync-apply-route.js +48 -6
  188. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  189. package/package.json +12 -12
@@ -320,7 +320,7 @@ CREATE TABLE IF NOT EXISTS _smrt_changes_pending (
320
320
  table_name TEXT NOT NULL,
321
321
  row_id TEXT,
322
322
  operation TEXT NOT NULL,
323
- tenant_id TEXT,
323
+ tenant_id UUID,
324
324
  created_at TIMESTAMPTZ NOT NULL
325
325
  );
326
326
 
@@ -330,7 +330,7 @@ CREATE INDEX IF NOT EXISTS idx_smrt_changes_pending_table
330
330
  /** Name of the PostgreSQL staging table for deferred change-feed appends. */
331
331
  var POSTGRES_CHANGE_FEED_PENDING_TABLE = "_smrt_changes_pending";
332
332
  /** PostgreSQL materialization of the portable change-feed table DDL. */
333
- var CREATE_POSTGRES_SMRT_CHANGES_TABLE = CREATE_SMRT_CHANGES_TABLE.replace(/\bTIMESTAMP\b/g, "TIMESTAMPTZ");
333
+ var CREATE_POSTGRES_SMRT_CHANGES_TABLE = CREATE_SMRT_CHANGES_TABLE.replace(/\bTIMESTAMP\b/g, "TIMESTAMPTZ").replace(/\btenant_id TEXT\b/g, "tenant_id UUID");
334
334
  /** PostgreSQL helper used to isolate best-effort feed appends (#2026). */
335
335
  var POSTGRES_CHANGE_FEED_APPEND_FUNCTION_NAME = "_smrt_append_change";
336
336
  /** PostgreSQL one-statement batch wrapper around the isolated append helper. */
@@ -350,11 +350,45 @@ var POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_IDENTITY = `${POSTGRES_CHANGE_FEE
350
350
  * replaced. Bump it whenever either helper's body changes in a way an existing
351
351
  * database must pick up.
352
352
  */
353
- var POSTGRES_CHANGE_FEED_HELPER_MARKER = "smrt-change-feed-helpers:v4";
353
+ var POSTGRES_CHANGE_FEED_HELPER_MARKER = "smrt-change-feed-helpers:v5";
354
+ /**
355
+ * The previous body marker, still stamped into every v5 body (#3297).
356
+ *
357
+ * v5 only changes how a tenant id is coerced: it is converted to whatever type
358
+ * the live `tenant_id` column has (TEXT before `db:migrate-uuid`, native uuid
359
+ * after), so a v5 body is a drop-in for v4 callers. Older SMRT processes probe
360
+ * for the v4 marker; carrying it stops them from replacing a v5 body with a
361
+ * v4 one (which inserts text into a converted uuid column) during a
362
+ * mixed-version rollout.
363
+ */
364
+ var POSTGRES_CHANGE_FEED_HELPER_COMPAT_MARKER = "smrt-change-feed-helpers:v4";
365
+ /**
366
+ * Coerce a text tenant id to the live type of `<table>.tenant_id` (#3297).
367
+ *
368
+ * `jsonb_populate_record` builds the table's row type at execution time, so
369
+ * the same body works before and after the column converts to uuid, and a
370
+ * cached plan is invalidated by the ALTER like any other. Empty strings are
371
+ * NULL, matching the TypeScript writers.
372
+ */
373
+ function postgresTenantIdAs(table, textExpression) {
374
+ return `(jsonb_populate_record(NULL::${table}, jsonb_build_object('tenant_id', NULLIF(${textExpression}, '')))).tenant_id`;
375
+ }
354
376
  /** PostgreSQL helper that sequences staged change-feed appends (#2649). */
355
377
  var POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME = "_smrt_drain_changes";
356
- /** Exact PostgreSQL identity used for catalog lookup of the drain helper. */
357
- var POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY = `${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME}(integer)`;
378
+ /**
379
+ * Exact PostgreSQL identity of the drain helper (#2982): the batch limit plus
380
+ * the live set of sensitive tables, which the drain purges from staging and
381
+ * never sequences.
382
+ */
383
+ var POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY = `${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME}(integer,text[])`;
384
+ /**
385
+ * Identity of the pre-#2982 single-argument drain, kept as a compatibility
386
+ * entry point so replicas of an older SMRT that still call
387
+ * `_smrt_drain_changes()` keep working during a mixed-version rollout. It
388
+ * delegates to the two-argument drain with no exclusions — exactly the old
389
+ * behavior — because it has no way to know the live sensitive set.
390
+ */
391
+ var POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION_IDENTITY = `${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME}(integer)`;
358
392
  /** How many staged entries one drain call sequences. */
359
393
  var POSTGRES_CHANGE_FEED_DRAIN_BATCH = 1e3;
360
394
  /**
@@ -380,10 +414,18 @@ var POSTGRES_CHANGE_FEED_DRAIN_BATCH = 1e3;
380
414
  * Failures are returned as data, like the append helper, so a drain problem
381
415
  * never aborts the caller's transaction. A failed drain leaves the staged rows
382
416
  * in place for the next attempt.
417
+ *
418
+ * Sensitive tables (#2982): `p_excluded_tables` is the caller's live
419
+ * sensitive set. Under the drain lock, staged rows for those tables are
420
+ * deleted before any sequence is allocated, and the sequencing batch skips
421
+ * them too, so an entry staged by an older replica (or left over from before
422
+ * an upgrade) never reaches `_smrt_changes`. Neither parameter has a default:
423
+ * a call with no arguments resolves only to the compatibility overload.
383
424
  */
384
425
  var CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION = `
385
426
  CREATE OR REPLACE FUNCTION ${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME}(
386
- p_limit INTEGER DEFAULT ${POSTGRES_CHANGE_FEED_DRAIN_BATCH}
427
+ p_limit INTEGER,
428
+ p_excluded_tables TEXT[]
387
429
  )
388
430
  RETURNS TABLE(
389
431
  drained_seq BIGINT,
@@ -398,6 +440,7 @@ LANGUAGE plpgsql
398
440
  SECURITY INVOKER
399
441
  AS $smrt_change_feed_drain$
400
442
  -- ${POSTGRES_CHANGE_FEED_HELPER_MARKER}
443
+ -- ${POSTGRES_CHANGE_FEED_HELPER_COMPAT_MARKER}
401
444
  DECLARE
402
445
  v_base BIGINT;
403
446
  v_error_code TEXT;
@@ -436,6 +479,23 @@ BEGIN
436
479
  RETURN;
437
480
  END IF;
438
481
 
482
+ -- The feed never records a sensitive table (#2982). Staged entries for
483
+ -- one — from an older replica, or staged before the table was declared
484
+ -- sensitive — are dropped here, before a sequence is allocated.
485
+ -- Bounded like the sequencing batch, so a large legacy backlog cannot make
486
+ -- one pass outlive statement_timeout and stall the feed; the remainder is
487
+ -- purged by later passes, and the batch below skips it meanwhile.
488
+ IF COALESCE(cardinality(p_excluded_tables), 0) > 0 THEN
489
+ DELETE FROM _smrt_changes_pending AS sensitive
490
+ WHERE sensitive.pending_id IN (
491
+ SELECT staged.pending_id
492
+ FROM _smrt_changes_pending AS staged
493
+ WHERE staged.table_name = ANY(p_excluded_tables)
494
+ ORDER BY staged.pending_id
495
+ LIMIT GREATEST(COALESCE(p_limit, ${POSTGRES_CHANGE_FEED_DRAIN_BATCH}), 1)
496
+ );
497
+ END IF;
498
+
439
499
  SELECT COALESCE(MAX(changes.seq), 0) INTO v_base FROM _smrt_changes AS changes;
440
500
 
441
501
  RETURN QUERY
@@ -444,6 +504,11 @@ BEGIN
444
504
  WHERE pending.pending_id IN (
445
505
  SELECT candidate.pending_id
446
506
  FROM _smrt_changes_pending AS candidate
507
+ -- A sensitive row committed after the purge above is skipped, not
508
+ -- sequenced; the next drain purges it.
509
+ WHERE NOT (
510
+ candidate.table_name = ANY(COALESCE(p_excluded_tables, ARRAY[]::TEXT[]))
511
+ )
447
512
  ORDER BY candidate.pending_id
448
513
  LIMIT GREATEST(COALESCE(p_limit, ${POSTGRES_CHANGE_FEED_DRAIN_BATCH}), 1)
449
514
  )
@@ -475,7 +540,7 @@ BEGIN
475
540
  numbered.table_name,
476
541
  numbered.row_id,
477
542
  numbered.operation,
478
- numbered.tenant_id,
543
+ ${postgresTenantIdAs("_smrt_changes", "numbered.tenant_id::TEXT")},
479
544
  numbered.created_at
480
545
  FROM numbered
481
546
  RETURNING
@@ -490,7 +555,7 @@ BEGIN
490
555
  inserted.table_name,
491
556
  inserted.row_id,
492
557
  inserted.operation,
493
- inserted.tenant_id,
558
+ inserted.tenant_id::TEXT,
494
559
  NULL::TEXT,
495
560
  NULL::TEXT
496
561
  FROM inserted
@@ -512,6 +577,54 @@ END;
512
577
  $smrt_change_feed_drain$;
513
578
  `;
514
579
  /**
580
+ * Compatibility single-argument drain (#2982): see
581
+ * {@link POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION_IDENTITY}. Current SMRT
582
+ * never calls it.
583
+ */
584
+ var CREATE_POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION = `
585
+ CREATE OR REPLACE FUNCTION ${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME}(
586
+ p_limit INTEGER DEFAULT ${POSTGRES_CHANGE_FEED_DRAIN_BATCH}
587
+ )
588
+ RETURNS TABLE(
589
+ drained_seq BIGINT,
590
+ drained_table TEXT,
591
+ drained_row_id TEXT,
592
+ drained_operation TEXT,
593
+ drained_tenant_id TEXT,
594
+ error_code TEXT,
595
+ error_message TEXT
596
+ )
597
+ LANGUAGE plpgsql
598
+ SECURITY INVOKER
599
+ AS $smrt_change_feed_drain_compat$
600
+ -- ${POSTGRES_CHANGE_FEED_HELPER_MARKER}
601
+ DECLARE
602
+ v_error_code TEXT;
603
+ v_error_message TEXT;
604
+ BEGIN
605
+ -- Its own boundary, like the drain it delegates to: a resolution or
606
+ -- privilege error on the inner call must come back as data, never abort an
607
+ -- older replica's caller transaction (#2026).
608
+ BEGIN
609
+ RETURN QUERY
610
+ SELECT * FROM ${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME}(p_limit, ARRAY[]::TEXT[]);
611
+ EXCEPTION WHEN query_canceled OR assert_failure OR OTHERS THEN
612
+ GET STACKED DIAGNOSTICS
613
+ v_error_code = RETURNED_SQLSTATE,
614
+ v_error_message = MESSAGE_TEXT;
615
+ RETURN QUERY SELECT
616
+ NULL::BIGINT,
617
+ NULL::TEXT,
618
+ NULL::TEXT,
619
+ NULL::TEXT,
620
+ NULL::TEXT,
621
+ v_error_code,
622
+ v_error_message;
623
+ END;
624
+ END;
625
+ $smrt_change_feed_drain_compat$;
626
+ `;
627
+ /**
515
628
  * PostgreSQL-only change-feed append function.
516
629
  *
517
630
  * A PL/pgSQL block with an EXCEPTION handler runs its body in an internal
@@ -538,6 +651,7 @@ LANGUAGE plpgsql
538
651
  SECURITY INVOKER
539
652
  AS $smrt_change_feed$
540
653
  -- ${POSTGRES_CHANGE_FEED_HELPER_MARKER}
654
+ -- ${POSTGRES_CHANGE_FEED_HELPER_COMPAT_MARKER}
541
655
  DECLARE
542
656
  v_seq BIGINT;
543
657
  v_error_code TEXT;
@@ -590,7 +704,7 @@ BEGIN
590
704
  p_table_name,
591
705
  p_row_id,
592
706
  p_operation,
593
- p_tenant_id,
707
+ ${postgresTenantIdAs("_smrt_changes_pending", "p_tenant_id")},
594
708
  p_created_at
595
709
  );
596
710
 
@@ -631,7 +745,7 @@ BEGIN
631
745
  p_table_name,
632
746
  p_row_id,
633
747
  p_operation,
634
- p_tenant_id,
748
+ ${postgresTenantIdAs("_smrt_changes", "p_tenant_id")},
635
749
  p_created_at
636
750
  FROM _smrt_changes AS changes
637
751
  RETURNING _smrt_changes.seq INTO v_seq;
@@ -666,6 +780,7 @@ LANGUAGE plpgsql
666
780
  SECURITY INVOKER
667
781
  AS $smrt_change_feed_batch$
668
782
  -- ${POSTGRES_CHANGE_FEED_HELPER_MARKER}
783
+ -- ${POSTGRES_CHANGE_FEED_HELPER_COMPAT_MARKER}
669
784
  DECLARE
670
785
  v_deferred BOOLEAN;
671
786
  v_error_code TEXT;
@@ -689,7 +804,7 @@ BEGIN
689
804
  input.value->>'table',
690
805
  input.value->>'rowId',
691
806
  input.value->>'operation',
692
- input.value->>'tenantId',
807
+ ${postgresTenantIdAs("_smrt_changes_pending", "input.value->>'tenantId'")},
693
808
  (input.value->>'timestamp')::TIMESTAMPTZ
694
809
  FROM input
695
810
  ORDER BY input.entry_index
@@ -721,7 +836,7 @@ BEGIN
721
836
  numbered.value->>'table',
722
837
  numbered.value->>'rowId',
723
838
  numbered.value->>'operation',
724
- numbered.value->>'tenantId',
839
+ ${postgresTenantIdAs("_smrt_changes", "numbered.value->>'tenantId'")},
725
840
  (numbered.value->>'timestamp')::TIMESTAMPTZ
726
841
  FROM numbered
727
842
  ORDER BY numbered.entry_index
@@ -747,6 +862,54 @@ var POSTGRES_CHANGE_FEED_PENDING_DDL = CREATE_POSTGRES_SMRT_CHANGES_PENDING_TABL
747
862
  ${statement}
748
863
  $smrt_change_feed_pending_ddl$;`).join("\n");
749
864
  /**
865
+ * Install both drain overloads inside a helper DO block (#2982).
866
+ *
867
+ * The two-argument drain is the first framework routine identity added to
868
+ * databases that already exist, and a new function gets PostgreSQL's default
869
+ * ACL — which a hardened deployment (PUBLIC EXECUTE revoked, the runtime role
870
+ * granted explicitly by the permissions planner) would leave the runtime
871
+ * unable to call. When this block creates it, it therefore inherits the
872
+ * compatibility drain's reconciled EXECUTE grants. A role that may not grant
873
+ * keeps the default ACL; the permissions planner reports and repairs it.
874
+ */
875
+ var POSTGRES_CHANGE_FEED_DRAIN_INSTALL = ` DECLARE
876
+ v_drain_created BOOLEAN :=
877
+ to_regprocedure('${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY}') IS NULL;
878
+ v_grant RECORD;
879
+ BEGIN
880
+ EXECUTE $smrt_change_feed_drain_ddl$
881
+ ${CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION.trim()}
882
+ $smrt_change_feed_drain_ddl$;
883
+ EXECUTE $smrt_change_feed_drain_compat_ddl$
884
+ ${CREATE_POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION.trim()}
885
+ $smrt_change_feed_drain_compat_ddl$;
886
+ IF v_drain_created AND (
887
+ SELECT p.proacl FROM pg_proc AS p
888
+ WHERE p.oid = to_regprocedure('${POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION_IDENTITY}')
889
+ ) IS NOT NULL THEN
890
+ BEGIN
891
+ EXECUTE 'REVOKE ALL ON FUNCTION ${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY} FROM PUBLIC';
892
+ FOR v_grant IN
893
+ SELECT acl.grantee
894
+ FROM pg_proc AS p, aclexplode(p.proacl) AS acl
895
+ WHERE p.oid = to_regprocedure('${POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION_IDENTITY}')
896
+ AND acl.privilege_type = 'EXECUTE'
897
+ LOOP
898
+ IF v_grant.grantee = 0 THEN
899
+ EXECUTE 'GRANT EXECUTE ON FUNCTION ${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY} TO PUBLIC';
900
+ ELSE
901
+ EXECUTE format(
902
+ 'GRANT EXECUTE ON FUNCTION ${POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY} TO %I',
903
+ pg_get_userbyid(v_grant.grantee)
904
+ );
905
+ END IF;
906
+ END LOOP;
907
+ EXCEPTION WHEN insufficient_privilege THEN
908
+ NULL;
909
+ END;
910
+ END IF;
911
+ END;`;
912
+ /**
750
913
  * Serialize PostgreSQL helper replacement inside one server-side statement.
751
914
  *
752
915
  * The transaction-scoped advisory lock prevents concurrent bootstraps from
@@ -762,9 +925,7 @@ BEGIN
762
925
  );
763
926
  ${POSTGRES_CHANGE_FEED_PENDING_DDL}
764
927
  DROP FUNCTION IF EXISTS ${LEGACY_POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY};
765
- EXECUTE $smrt_change_feed_drain_ddl$
766
- ${CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION.trim()}
767
- $smrt_change_feed_drain_ddl$;
928
+ ${POSTGRES_CHANGE_FEED_DRAIN_INSTALL}
768
929
  EXECUTE $smrt_change_feed_ddl$
769
930
  ${CREATE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION.trim()}
770
931
  $smrt_change_feed_ddl$;
@@ -809,9 +970,7 @@ BEGIN
809
970
  );
810
971
  ${POSTGRES_CHANGE_FEED_PENDING_DDL}
811
972
  DROP FUNCTION IF EXISTS ${LEGACY_POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY};
812
- EXECUTE $smrt_change_feed_drain_ddl$
813
- ${CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION.trim()}
814
- $smrt_change_feed_drain_ddl$;
973
+ ${POSTGRES_CHANGE_FEED_DRAIN_INSTALL}
815
974
  IF v_append_stale THEN
816
975
  EXECUTE $smrt_change_feed_ddl$
817
976
  ${CREATE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION.trim()}
@@ -855,9 +1014,7 @@ ${statement}
855
1014
  $smrt_change_feed_schema_ddl$;`).join("\n")}
856
1015
  ${POSTGRES_CHANGE_FEED_PENDING_DDL}
857
1016
  DROP FUNCTION IF EXISTS ${LEGACY_POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY};
858
- EXECUTE $smrt_change_feed_drain_ddl$
859
- ${CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION.trim()}
860
- $smrt_change_feed_drain_ddl$;
1017
+ ${POSTGRES_CHANGE_FEED_DRAIN_INSTALL}
861
1018
  IF v_append_stale THEN
862
1019
  EXECUTE $smrt_change_feed_ddl$
863
1020
  ${CREATE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION.trim()}
@@ -986,9 +1143,49 @@ var FRAMEWORK_OPERATIONAL_TABLES = [
986
1143
  */
987
1144
  var RETIRED_SYSTEM_TABLES = ["_smrt_registry", "_smrt_signals"];
988
1145
  /**
1146
+ * System-table columns that hold a real tenant id (#3297): a tenant UUID from
1147
+ * the tenant context, or NULL for global rows. On PostgreSQL they materialize
1148
+ * as native `uuid`, like every application `tenant_id`; other engines keep
1149
+ * TEXT, where `UUID` renders as TEXT anyway. Existing PostgreSQL TEXT columns
1150
+ * convert through `smrt db:migrate-uuid` in a maintenance window.
1151
+ *
1152
+ * Deliberately NOT here (they stay TEXT):
1153
+ * - text identity encodings that are not tenant ids: `_smrt_contexts.owner_id`
1154
+ * (`__collection__` / `__collection__:<tenantId>` scope keys, object/agent
1155
+ * row ids), `_smrt_run_once_claims.actor` (an arbitration namespace),
1156
+ * `_smrt_dispatch.processed_by` (a subscriber name) and
1157
+ * `_smrt_schema_migrations.applied_by` (a hostname);
1158
+ * - `_smrt_run_once_claims.tenant_id`: a NOT NULL claim scope folded into the
1159
+ * claim key; a caller without a tenant must pass a scope string, so it is a
1160
+ * scope key, not a tenant FK;
1161
+ * - `_smrt_ai_usage.tenant_id`: model usage writes tenant UUIDs, but the
1162
+ * published external-agent usage API accepts any non-empty text tenant id,
1163
+ * so converting it is a contract change tracked separately.
1164
+ */
1165
+ var SYSTEM_TABLE_TENANT_UUID_COLUMNS = Object.freeze([
1166
+ {
1167
+ table: "_smrt_dispatch",
1168
+ column: "tenant_id"
1169
+ },
1170
+ {
1171
+ table: "_smrt_dispatch_subscriptions",
1172
+ column: "tenant_id"
1173
+ },
1174
+ {
1175
+ table: "_smrt_changes",
1176
+ column: "tenant_id"
1177
+ },
1178
+ {
1179
+ table: "_smrt_changes_pending",
1180
+ column: "tenant_id"
1181
+ }
1182
+ ]);
1183
+ /**
989
1184
  * Materialize the portable system-table schema for a target engine.
990
1185
  *
991
- * PostgreSQL must use TIMESTAMPTZ because every system Date is an instant.
1186
+ * PostgreSQL must use TIMESTAMPTZ because every system Date is an instant,
1187
+ * and native UUID for the tenant columns in
1188
+ * {@link SYSTEM_TABLE_TENANT_UUID_COLUMNS} (#3297).
992
1189
  * PostgreSQL and DuckDB also materialize integer storage as BIGINT, matching
993
1190
  * application-table DDL; SQLite keeps its variable-width INTEGER.
994
1191
  */
@@ -998,7 +1195,17 @@ function getSystemTableDDL(engine) {
998
1195
  /** Materialize one portable system-table statement for a target engine. */
999
1196
  function getSystemTableDDLForEngine(ddl, engine) {
1000
1197
  const integerDdl = engine === "postgres" || engine === "duckdb" || engine === "json" ? ddl.replace(/\bINTEGER\b/g, "BIGINT") : ddl;
1001
- return engine === "postgres" ? integerDdl.replace(/\bTIMESTAMP\b/g, "TIMESTAMPTZ") : integerDdl;
1198
+ return engine === "postgres" ? toPostgresTenantUuid(integerDdl.replace(/\bTIMESTAMP\b/g, "TIMESTAMPTZ")) : integerDdl;
1199
+ }
1200
+ var POSTGRES_UUID_TENANT_TABLES = new Set(SYSTEM_TABLE_TENANT_UUID_COLUMNS.map(({ table }) => table));
1201
+ /**
1202
+ * Render `tenant_id TEXT` as native uuid on PostgreSQL in the DDL of a table
1203
+ * listed in {@link SYSTEM_TABLE_TENANT_UUID_COLUMNS} (each system DDL
1204
+ * constant creates one table).
1205
+ */
1206
+ function toPostgresTenantUuid(ddl) {
1207
+ const table = ddl.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z_][\w$]*)/i)?.[1];
1208
+ return table && POSTGRES_UUID_TENANT_TABLES.has(table) ? ddl.replace(/\btenant_id TEXT\b/g, "tenant_id UUID") : ddl;
1002
1209
  }
1003
1210
  /**
1004
1211
  * Current SMRT system schema version.
@@ -1034,6 +1241,6 @@ Object.freeze({
1034
1241
  "1.11.1": "98112937a3bbb3883c3db1fd5e836751f91a479c1c5da64d4410263a6581ef1f"
1035
1242
  });
1036
1243
  //#endregion
1037
- export { ALL_SYSTEM_TABLES, CREATE_POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION, CREATE_POSTGRES_SMRT_CHANGES_PENDING_TABLE, CREATE_POSTGRES_SMRT_CHANGES_TABLE, CREATE_SMRT_AI_USAGE_TABLE, CREATE_SMRT_BACKFILLS_TABLE, CREATE_SMRT_CHANGES_TABLE, CREATE_SMRT_CONTEXTS_TABLE, CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE, CREATE_SMRT_DISPATCH_TABLE, CREATE_SMRT_EMBEDDINGS_TABLE, CREATE_SMRT_MIGRATIONS_TABLE, CREATE_SMRT_RUN_ONCE_CLAIMS_TABLE, CREATE_SMRT_SCHEMA_MIGRATIONS_TABLE, ENSURE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, ENSURE_POSTGRES_CHANGE_FEED_SCHEMA, FRAMEWORK_OPERATIONAL_TABLES, LEGACY_POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_NAME, POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_FUNCTION_NAME, POSTGRES_CHANGE_FEED_DRAIN_BATCH, POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME, POSTGRES_CHANGE_FEED_HELPER_MARKER, POSTGRES_CHANGE_FEED_PENDING_TABLE, REPLACE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, RETIRED_SYSTEM_TABLES, RUN_ONCE_CLAIMS_TABLE, SMRT_SCHEMA_VERSION, getSystemTableDDL, getSystemTableDDLForEngine };
1244
+ export { ALL_SYSTEM_TABLES, CREATE_POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION, CREATE_POSTGRES_SMRT_CHANGES_PENDING_TABLE, CREATE_POSTGRES_SMRT_CHANGES_TABLE, CREATE_SMRT_AI_USAGE_TABLE, CREATE_SMRT_BACKFILLS_TABLE, CREATE_SMRT_CHANGES_TABLE, CREATE_SMRT_CONTEXTS_TABLE, CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE, CREATE_SMRT_DISPATCH_TABLE, CREATE_SMRT_EMBEDDINGS_TABLE, CREATE_SMRT_MIGRATIONS_TABLE, CREATE_SMRT_RUN_ONCE_CLAIMS_TABLE, CREATE_SMRT_SCHEMA_MIGRATIONS_TABLE, ENSURE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, ENSURE_POSTGRES_CHANGE_FEED_SCHEMA, FRAMEWORK_OPERATIONAL_TABLES, LEGACY_POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_NAME, POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_FUNCTION_NAME, POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_DRAIN_BATCH, POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME, POSTGRES_CHANGE_FEED_HELPER_COMPAT_MARKER, POSTGRES_CHANGE_FEED_HELPER_MARKER, POSTGRES_CHANGE_FEED_PENDING_TABLE, REPLACE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, RETIRED_SYSTEM_TABLES, RUN_ONCE_CLAIMS_TABLE, SMRT_SCHEMA_VERSION, SYSTEM_TABLE_TENANT_UUID_COLUMNS, getSystemTableDDL, getSystemTableDDLForEngine };
1038
1245
 
1039
1246
  //# sourceMappingURL=schema.js.map