@opengeni/db 0.31.1 → 0.36.1

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 (125) hide show
  1. package/dist/browser-auth.d.ts +209 -1
  2. package/dist/browser-downloads.d.ts +51 -0
  3. package/dist/browser-identities.d.ts +11 -2
  4. package/dist/browser-sessions.d.ts +83 -1
  5. package/dist/browser-state-artifacts.d.ts +75 -1
  6. package/dist/canonical-human-identities.d.ts +48 -0
  7. package/dist/canonical-human-identities.js +28 -0
  8. package/dist/canonical-human-identities.js.map +1 -0
  9. package/dist/capability-integrations.d.ts +8 -6
  10. package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
  11. package/dist/chunk-5KTIEDX7.js.map +1 -0
  12. package/dist/chunk-FPVAD4CO.js +117 -0
  13. package/dist/chunk-FPVAD4CO.js.map +1 -0
  14. package/dist/chunk-GYO7ED2Q.js +259 -0
  15. package/dist/chunk-GYO7ED2Q.js.map +1 -0
  16. package/dist/{chunk-F5FT7BEZ.js → chunk-HSOWSYOX.js} +2 -2
  17. package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
  18. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  19. package/dist/{chunk-NBE6CHRI.js → chunk-VTPRIXKG.js} +49 -9
  20. package/dist/chunk-VTPRIXKG.js.map +1 -0
  21. package/dist/{chunk-ISI2TIUG.js → chunk-Z7VYMNPZ.js} +76 -4
  22. package/dist/chunk-Z7VYMNPZ.js.map +1 -0
  23. package/dist/database.d.ts +5 -0
  24. package/dist/editable-artifact-durable-export.js +2 -2
  25. package/dist/editable-artifacts.js +3 -3
  26. package/dist/index.d.ts +467 -29
  27. package/dist/index.js +23247 -16232
  28. package/dist/index.js.map +1 -1
  29. package/dist/insights.d.ts +23 -7
  30. package/dist/integration-bindings.d.ts +24 -24
  31. package/dist/integration-facets.d.ts +99 -0
  32. package/dist/interaction-revisions.d.ts +11 -0
  33. package/dist/interaction-schema.d.ts +698 -34
  34. package/dist/persistence-errors.d.ts +8 -0
  35. package/dist/provision-roles.js +1 -1
  36. package/dist/runtime-posture.d.ts +8 -7
  37. package/dist/schema.d.ts +9464 -5412
  38. package/dist/schema.js +51 -9
  39. package/dist/session-control.d.ts +29 -3
  40. package/dist/session-tenancy.d.ts +44 -0
  41. package/dist/session-tenancy.js +16 -0
  42. package/dist/session-tenancy.js.map +1 -0
  43. package/dist/slack-task-policy-schema.d.ts +847 -0
  44. package/dist/slack-task-policy.d.ts +65 -0
  45. package/dist/task-notes-schema.d.ts +924 -0
  46. package/dist/task-notes.d.ts +85 -0
  47. package/dist/video-generation.d.ts +9 -0
  48. package/dist/video-generation.js +5 -3
  49. package/dist/xai-subscription.d.ts +326 -0
  50. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  51. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  52. package/drizzle/0214_browser_download_saves.sql +34 -0
  53. package/drizzle/0215_browser_controller_host.sql +385 -0
  54. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  55. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  56. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  57. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  58. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  59. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  60. package/drizzle/0220_session_channels.sql +54 -0
  61. package/drizzle/0221_sessions_channel_index.sql +5 -0
  62. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  63. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  64. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  65. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  66. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  67. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  68. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  69. package/drizzle/0227_slack_native_actions.sql +145 -0
  70. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  71. package/drizzle/0228_slack_task_policy.sql +400 -0
  72. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  73. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  74. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  75. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  76. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  77. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  78. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  79. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  80. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  81. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  82. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  83. package/drizzle/0238_recover_unclaimed_session_turns.sql +307 -0
  84. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  85. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  86. package/drizzle/0239_task_tree_notes.sql +990 -0
  87. package/package.json +13 -5
  88. package/src/browser-auth.ts +2600 -123
  89. package/src/browser-downloads.ts +517 -0
  90. package/src/browser-identities.ts +220 -3
  91. package/src/browser-sessions.ts +334 -45
  92. package/src/browser-state-artifacts.ts +402 -12
  93. package/src/canonical-human-identities.ts +317 -0
  94. package/src/capability-integrations.ts +121 -213
  95. package/src/database.ts +41 -1
  96. package/src/index.ts +4625 -411
  97. package/src/insights.ts +109 -28
  98. package/src/integration-bindings.ts +101 -101
  99. package/src/{integration-features.ts → integration-facets.ts} +187 -184
  100. package/src/interaction-revisions.ts +33 -3
  101. package/src/interaction-schema.ts +311 -8
  102. package/src/pack-components.ts +43 -43
  103. package/src/pack-installations.ts +0 -43
  104. package/src/persistence-errors.ts +60 -1
  105. package/src/plugin-packages.ts +9 -9
  106. package/src/provision-roles.ts +232 -0
  107. package/src/runtime-posture.ts +227 -14
  108. package/src/schema.ts +1188 -75
  109. package/src/session-control.ts +122 -37
  110. package/src/session-queue-commands.ts +78 -1
  111. package/src/session-tenancy.ts +188 -0
  112. package/src/session-tool-call-settlement.ts +9 -3
  113. package/src/slack-task-policy-schema.ts +130 -0
  114. package/src/slack-task-policy.ts +341 -0
  115. package/src/task-notes-schema.ts +135 -0
  116. package/src/task-notes.ts +178 -0
  117. package/src/video-generation.ts +90 -12
  118. package/src/workspace-artifacts.ts +1 -1
  119. package/src/xai-subscription.ts +1887 -0
  120. package/dist/chunk-ISI2TIUG.js.map +0 -1
  121. package/dist/chunk-MWDVXQOL.js.map +0 -1
  122. package/dist/chunk-NBE6CHRI.js.map +0 -1
  123. package/dist/chunk-YHZXQ7HP.js.map +0 -1
  124. package/dist/integration-features.d.ts +0 -99
  125. /package/dist/{chunk-F5FT7BEZ.js.map → chunk-HSOWSYOX.js.map} +0 -0
@@ -0,0 +1,610 @@
1
+ -- deployment-mode: maintenance
2
+ -- Replace the Integration Feature submodel with authoritative Integration
3
+ -- Facet definitions and bindings across physical storage, Pack manifests,
4
+ -- ownership, and operation results. This is a one-way protocol cutover: all
5
+ -- opengeni_app sessions must be stopped and in-flight Pack/Facet operations
6
+ -- must be settled before activation.
7
+
8
+ SET LOCAL lock_timeout = '5s';
9
+ SET LOCAL statement_timeout = '30min';
10
+
11
+ DO $maintenance_preflight_guard$
12
+ BEGIN
13
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
14
+ AND EXISTS (
15
+ SELECT 1
16
+ FROM pg_stat_activity
17
+ WHERE datname = current_database()
18
+ AND usename = 'opengeni_app'
19
+ AND pid <> pg_backend_pid()
20
+ )
21
+ THEN
22
+ RAISE EXCEPTION
23
+ 'Integration Facet activation requires all opengeni_app sessions to be stopped'
24
+ USING ERRCODE = '55000';
25
+ END IF;
26
+
27
+ IF EXISTS (
28
+ SELECT 1
29
+ FROM capability_operations
30
+ WHERE target_kind IN ('pack', 'facet_binding')
31
+ AND status IN ('pending', 'running')
32
+ ) THEN
33
+ RAISE EXCEPTION
34
+ 'Integration Facet activation requires Pack and Facet operations to be settled'
35
+ USING ERRCODE = '55000';
36
+ END IF;
37
+ END
38
+ $maintenance_preflight_guard$;
39
+
40
+ DO $lock_authority$
41
+ DECLARE
42
+ relation_name text;
43
+ BEGIN
44
+ FOREACH relation_name IN ARRAY ARRAY[
45
+ 'integration_feature_binding_owners',
46
+ 'integration_facet_binding_owners',
47
+ 'integration_feature_bindings',
48
+ 'integration_facet_bindings',
49
+ 'integration_feature_facets',
50
+ 'integration_facet_definitions',
51
+ 'pack_installation_components',
52
+ 'pack_installations',
53
+ 'workspace_packs',
54
+ 'capability_installations',
55
+ 'capability_operations'
56
+ ]
57
+ LOOP
58
+ IF to_regclass(relation_name) IS NOT NULL THEN
59
+ EXECUTE format('LOCK TABLE %I IN ACCESS EXCLUSIVE MODE', relation_name);
60
+ END IF;
61
+ END LOOP;
62
+ END
63
+ $lock_authority$;
64
+
65
+ DO $facet_precondition$
66
+ BEGIN
67
+ IF EXISTS (
68
+ SELECT 1
69
+ FROM workspace_packs pack
70
+ WHERE pack.manifest ? 'components'
71
+ AND jsonb_typeof(pack.manifest -> 'components') <> 'array'
72
+ ) OR EXISTS (
73
+ SELECT 1
74
+ FROM pack_installations installation
75
+ WHERE installation.manifest_snapshot IS NOT NULL
76
+ AND installation.manifest_snapshot ? 'components'
77
+ AND jsonb_typeof(installation.manifest_snapshot -> 'components') <> 'array'
78
+ ) THEN
79
+ RAISE EXCEPTION 'a persisted Pack has a non-array components manifest'
80
+ USING ERRCODE = '23514';
81
+ END IF;
82
+
83
+ IF EXISTS (
84
+ SELECT 1
85
+ FROM workspace_packs pack,
86
+ LATERAL jsonb_array_elements(COALESCE(pack.manifest -> 'components', '[]'::jsonb)) component
87
+ WHERE (
88
+ component ->> 'kind' = 'feature'
89
+ AND (
90
+ NULLIF(component ->> 'featureKey', '') IS NULL
91
+ OR component ? 'facetKey'
92
+ )
93
+ ) OR (
94
+ component ? 'featureKey'
95
+ AND component ->> 'kind' <> 'feature'
96
+ )
97
+ ) OR EXISTS (
98
+ SELECT 1
99
+ FROM pack_installations installation,
100
+ LATERAL jsonb_array_elements(COALESCE(installation.manifest_snapshot -> 'components', '[]'::jsonb)) component
101
+ WHERE installation.manifest_snapshot IS NOT NULL
102
+ AND (
103
+ (
104
+ component ->> 'kind' = 'feature'
105
+ AND (
106
+ NULLIF(component ->> 'featureKey', '') IS NULL
107
+ OR component ? 'facetKey'
108
+ )
109
+ ) OR (
110
+ component ? 'featureKey'
111
+ AND component ->> 'kind' <> 'feature'
112
+ )
113
+ )
114
+ ) THEN
115
+ RAISE EXCEPTION 'a persisted Pack Facet component has ambiguous identity'
116
+ USING ERRCODE = '23514';
117
+ END IF;
118
+ END
119
+ $facet_precondition$;
120
+
121
+ CREATE OR REPLACE FUNCTION opengeni_private.integration_facet_rewrite_pack(value jsonb)
122
+ RETURNS jsonb
123
+ LANGUAGE plpgsql
124
+ IMMUTABLE
125
+ STRICT
126
+ PARALLEL SAFE
127
+ AS $rewrite_pack$
128
+ DECLARE
129
+ components jsonb;
130
+ BEGIN
131
+ IF NOT (value ? 'components') THEN
132
+ RETURN value;
133
+ END IF;
134
+ IF jsonb_typeof(value -> 'components') <> 'array' THEN
135
+ RAISE EXCEPTION 'Pack components must be an array' USING ERRCODE = '23514';
136
+ END IF;
137
+
138
+ SELECT COALESCE(
139
+ jsonb_agg(
140
+ CASE
141
+ WHEN component.value ->> 'kind' = 'feature' THEN
142
+ (component.value - 'kind' - 'featureKey') || jsonb_build_object(
143
+ 'kind', 'facet',
144
+ 'facetKey', component.value -> 'featureKey'
145
+ )
146
+ ELSE component.value
147
+ END
148
+ ORDER BY component.ordinality
149
+ ),
150
+ '[]'::jsonb
151
+ )
152
+ INTO components
153
+ FROM jsonb_array_elements(value -> 'components') WITH ORDINALITY AS component(value, ordinality);
154
+
155
+ RETURN jsonb_set(value, '{components}', components, false);
156
+ END
157
+ $rewrite_pack$;
158
+
159
+ CREATE OR REPLACE FUNCTION opengeni_private.integration_facet_canonical_json(value jsonb)
160
+ RETURNS text
161
+ LANGUAGE plpgsql
162
+ IMMUTABLE
163
+ STRICT
164
+ PARALLEL SAFE
165
+ AS $canonical_json$
166
+ DECLARE
167
+ rendered text;
168
+ BEGIN
169
+ CASE jsonb_typeof(value)
170
+ WHEN 'object' THEN
171
+ SELECT COALESCE(
172
+ '{' || string_agg(
173
+ to_jsonb(entry.key)::text || ':' ||
174
+ opengeni_private.integration_facet_canonical_json(entry.value),
175
+ ',' ORDER BY entry.key COLLATE "C"
176
+ ) || '}',
177
+ '{}'
178
+ )
179
+ INTO rendered
180
+ FROM jsonb_each(value) AS entry(key, value);
181
+ RETURN rendered;
182
+ WHEN 'array' THEN
183
+ SELECT COALESCE(
184
+ '[' || string_agg(
185
+ opengeni_private.integration_facet_canonical_json(entry.value),
186
+ ',' ORDER BY entry.ordinality
187
+ ) || ']',
188
+ '[]'
189
+ )
190
+ INTO rendered
191
+ FROM jsonb_array_elements(value) WITH ORDINALITY AS entry(value, ordinality);
192
+ RETURN rendered;
193
+ ELSE
194
+ RETURN value::text;
195
+ END CASE;
196
+ END
197
+ $canonical_json$;
198
+
199
+ UPDATE workspace_packs
200
+ SET manifest = opengeni_private.integration_facet_rewrite_pack(manifest)
201
+ WHERE manifest ? 'components'
202
+ AND manifest IS DISTINCT FROM opengeni_private.integration_facet_rewrite_pack(manifest);
203
+
204
+ CREATE TEMP TABLE integration_facet_pack_cutover ON COMMIT DROP AS
205
+ SELECT
206
+ id,
207
+ workspace_id,
208
+ pack_id,
209
+ opengeni_private.integration_facet_rewrite_pack(manifest_snapshot) AS manifest_snapshot
210
+ FROM pack_installations
211
+ WHERE manifest_snapshot IS NOT NULL
212
+ AND manifest_snapshot IS DISTINCT FROM
213
+ opengeni_private.integration_facet_rewrite_pack(manifest_snapshot);
214
+
215
+ UPDATE pack_installations installation
216
+ SET manifest_snapshot = migrated.manifest_snapshot,
217
+ manifest_digest = encode(
218
+ digest(
219
+ convert_to(
220
+ opengeni_private.integration_facet_canonical_json(migrated.manifest_snapshot),
221
+ 'UTF8'
222
+ ),
223
+ 'sha256'
224
+ ),
225
+ 'hex'
226
+ )
227
+ FROM integration_facet_pack_cutover migrated
228
+ WHERE installation.id = migrated.id;
229
+
230
+ UPDATE capability_installations capability
231
+ SET metadata = jsonb_set(
232
+ capability.metadata,
233
+ '{manifestDigest}',
234
+ to_jsonb(installation.manifest_digest),
235
+ true
236
+ )
237
+ FROM pack_installations installation
238
+ JOIN integration_facet_pack_cutover migrated ON migrated.id = installation.id
239
+ WHERE capability.workspace_id = migrated.workspace_id
240
+ AND capability.capability_id = 'pack:' || migrated.pack_id
241
+ AND capability.metadata ->> 'platformVersion' = '2';
242
+
243
+ DELETE FROM capability_operations operation
244
+ USING integration_facet_pack_cutover migrated
245
+ WHERE operation.workspace_id = migrated.workspace_id
246
+ AND operation.target_kind = 'pack'
247
+ AND operation.target_id = migrated.pack_id;
248
+
249
+ ALTER TABLE pack_installation_components
250
+ DROP CONSTRAINT IF EXISTS pack_installation_components_kind_chk;
251
+
252
+ UPDATE pack_installation_components
253
+ SET kind = CASE WHEN kind = 'feature' THEN 'facet' ELSE kind END,
254
+ metadata = CASE
255
+ WHEN metadata ? 'featureKey' THEN
256
+ (metadata - 'featureKey') || jsonb_build_object('facetKey', metadata -> 'featureKey')
257
+ ELSE metadata
258
+ END
259
+ WHERE kind = 'feature' OR metadata ? 'featureKey';
260
+
261
+ ALTER TABLE pack_installation_components
262
+ ADD CONSTRAINT pack_installation_components_kind_chk CHECK (
263
+ kind IN ('plugin', 'skill', 'integration', 'facet', 'inline_skill')
264
+ ) NOT VALID;
265
+ ALTER TABLE pack_installation_components
266
+ VALIDATE CONSTRAINT pack_installation_components_kind_chk;
267
+
268
+ DELETE FROM capability_operations
269
+ WHERE target_kind = 'facet_binding'
270
+ AND result IS NOT NULL
271
+ AND (
272
+ result ? 'featureKey'
273
+ OR (jsonb_typeof(result -> 'binding') = 'object' AND result -> 'binding' ? 'featureKey')
274
+ );
275
+
276
+ DO $drop_legacy_triggers$
277
+ BEGIN
278
+ IF to_regclass('integration_feature_bindings') IS NOT NULL THEN
279
+ EXECUTE 'DROP TRIGGER IF EXISTS integration_feature_bindings_validate ON integration_feature_bindings';
280
+ END IF;
281
+ IF to_regclass('integration_facet_bindings') IS NOT NULL THEN
282
+ EXECUTE 'DROP TRIGGER IF EXISTS integration_feature_bindings_validate ON integration_facet_bindings';
283
+ EXECUTE 'DROP TRIGGER IF EXISTS integration_facet_bindings_validate ON integration_facet_bindings';
284
+ END IF;
285
+ IF to_regclass('integration_feature_binding_owners') IS NOT NULL THEN
286
+ EXECUTE 'DROP TRIGGER IF EXISTS integration_feature_binding_owners_validate ON integration_feature_binding_owners';
287
+ END IF;
288
+ IF to_regclass('integration_facet_binding_owners') IS NOT NULL THEN
289
+ EXECUTE 'DROP TRIGGER IF EXISTS integration_feature_binding_owners_validate ON integration_facet_binding_owners';
290
+ EXECUTE 'DROP TRIGGER IF EXISTS integration_facet_binding_owners_validate ON integration_facet_binding_owners';
291
+ END IF;
292
+ END
293
+ $drop_legacy_triggers$;
294
+
295
+ DROP FUNCTION IF EXISTS capability_v2_validate_feature_binding();
296
+ DROP FUNCTION IF EXISTS capability_v2_validate_feature_binding_owner();
297
+
298
+ DO $rename_relations$
299
+ BEGIN
300
+ IF to_regclass('integration_feature_facets') IS NOT NULL
301
+ AND to_regclass('integration_facet_definitions') IS NULL
302
+ THEN
303
+ ALTER TABLE integration_feature_facets RENAME TO integration_facet_definitions;
304
+ END IF;
305
+ IF to_regclass('integration_feature_bindings') IS NOT NULL
306
+ AND to_regclass('integration_facet_bindings') IS NULL
307
+ THEN
308
+ ALTER TABLE integration_feature_bindings RENAME TO integration_facet_bindings;
309
+ END IF;
310
+ IF to_regclass('integration_feature_binding_owners') IS NOT NULL
311
+ AND to_regclass('integration_facet_binding_owners') IS NULL
312
+ THEN
313
+ ALTER TABLE integration_feature_binding_owners RENAME TO integration_facet_binding_owners;
314
+ END IF;
315
+ END
316
+ $rename_relations$;
317
+
318
+ DO $rename_columns$
319
+ BEGIN
320
+ IF EXISTS (
321
+ SELECT 1 FROM information_schema.columns
322
+ WHERE table_schema = current_schema()
323
+ AND table_name = 'integration_facet_definitions'
324
+ AND column_name = 'feature_key'
325
+ ) THEN
326
+ ALTER TABLE integration_facet_definitions RENAME COLUMN feature_key TO facet_key;
327
+ END IF;
328
+ IF EXISTS (
329
+ SELECT 1 FROM information_schema.columns
330
+ WHERE table_schema = current_schema()
331
+ AND table_name = 'integration_facet_bindings'
332
+ AND column_name = 'feature_facet_id'
333
+ ) THEN
334
+ ALTER TABLE integration_facet_bindings RENAME COLUMN feature_facet_id TO facet_definition_id;
335
+ END IF;
336
+ END
337
+ $rename_columns$;
338
+
339
+ DO $rename_constraints$
340
+ DECLARE
341
+ target_table text;
342
+ old_name text;
343
+ new_name text;
344
+ constraint_row record;
345
+ BEGIN
346
+ FOREACH target_table IN ARRAY ARRAY[
347
+ 'integration_facet_definitions',
348
+ 'integration_facet_bindings',
349
+ 'integration_facet_binding_owners'
350
+ ]
351
+ LOOP
352
+ FOR constraint_row IN
353
+ SELECT conname
354
+ FROM pg_constraint
355
+ WHERE conrelid = to_regclass(target_table)
356
+ AND conname LIKE '%feature%'
357
+ ORDER BY conname
358
+ LOOP
359
+ old_name := constraint_row.conname;
360
+ new_name := replace(old_name, 'integration_feature_facets', 'integration_facet_definitions');
361
+ new_name := replace(new_name, 'integration_feature_bindings', 'integration_facet_bindings');
362
+ new_name := replace(new_name, 'integration_feature_binding_owners', 'integration_facet_binding_owners');
363
+ new_name := replace(new_name, 'feature_facet', 'facet_definition');
364
+ new_name := replace(new_name, 'feature_key', 'facet_key');
365
+ new_name := replace(new_name, 'integration_feature', 'integration_facet');
366
+ new_name := replace(new_name, 'feature', 'facet');
367
+ IF old_name <> new_name THEN
368
+ EXECUTE format('ALTER TABLE %I RENAME CONSTRAINT %I TO %I', target_table, old_name, new_name);
369
+ END IF;
370
+ END LOOP;
371
+ END LOOP;
372
+ END
373
+ $rename_constraints$;
374
+
375
+ DO $rename_indexes$
376
+ DECLARE
377
+ index_row record;
378
+ new_name text;
379
+ BEGIN
380
+ FOR index_row IN
381
+ SELECT indexname
382
+ FROM pg_indexes
383
+ WHERE schemaname = current_schema()
384
+ AND tablename IN (
385
+ 'integration_facet_definitions',
386
+ 'integration_facet_bindings',
387
+ 'integration_facet_binding_owners'
388
+ )
389
+ AND indexname LIKE '%feature%'
390
+ ORDER BY indexname
391
+ LOOP
392
+ new_name := replace(index_row.indexname, 'integration_feature_facets', 'integration_facet_definitions');
393
+ new_name := replace(new_name, 'integration_feature_bindings', 'integration_facet_bindings');
394
+ new_name := replace(new_name, 'integration_feature_binding_owners', 'integration_facet_binding_owners');
395
+ new_name := replace(new_name, 'feature_facet', 'facet_definition');
396
+ new_name := replace(new_name, 'feature_key', 'facet_key');
397
+ new_name := replace(new_name, 'integration_feature', 'integration_facet');
398
+ new_name := replace(new_name, 'feature', 'facet');
399
+ IF index_row.indexname <> new_name THEN
400
+ EXECUTE format('ALTER INDEX %I RENAME TO %I', index_row.indexname, new_name);
401
+ END IF;
402
+ END LOOP;
403
+ END
404
+ $rename_indexes$;
405
+
406
+ UPDATE integration_facet_binding_owners
407
+ SET owner_id = 'facet:' || substr(owner_id, length('feature:') + 1)
408
+ WHERE owner_kind = 'direct'
409
+ AND owner_id LIKE 'feature:%';
410
+
411
+ CREATE OR REPLACE FUNCTION capability_v2_validate_facet_binding()
412
+ RETURNS trigger
413
+ LANGUAGE plpgsql
414
+ AS $$
415
+ BEGIN
416
+ IF NOT EXISTS (
417
+ SELECT 1
418
+ FROM integration_facet_definitions definition
419
+ JOIN capability_facet_installations installation
420
+ ON installation.id = NEW.integration_facet_installation_id
421
+ AND installation.facet_id = definition.integration_facet_id
422
+ WHERE definition.id = NEW.facet_definition_id
423
+ AND installation.account_id = NEW.account_id
424
+ AND installation.workspace_id = NEW.workspace_id
425
+ ) THEN
426
+ RAISE EXCEPTION 'Facet binding does not match its Integration installation or tenant'
427
+ USING ERRCODE = '23514';
428
+ END IF;
429
+ IF NEW.connection_id IS NOT NULL AND NOT EXISTS (
430
+ SELECT 1 FROM connections connection
431
+ WHERE connection.id = NEW.connection_id
432
+ AND connection.account_id = NEW.account_id
433
+ AND connection.workspace_id = NEW.workspace_id
434
+ ) THEN
435
+ RAISE EXCEPTION 'Facet binding Connection belongs to another tenant'
436
+ USING ERRCODE = '23514';
437
+ END IF;
438
+ RETURN NEW;
439
+ END;
440
+ $$;
441
+
442
+ CREATE TRIGGER integration_facet_bindings_validate
443
+ BEFORE INSERT OR UPDATE ON integration_facet_bindings
444
+ FOR EACH ROW EXECUTE FUNCTION capability_v2_validate_facet_binding();
445
+
446
+ CREATE OR REPLACE FUNCTION capability_v2_validate_facet_binding_owner()
447
+ RETURNS trigger
448
+ LANGUAGE plpgsql
449
+ AS $$
450
+ BEGIN
451
+ IF NOT EXISTS (
452
+ SELECT 1 FROM integration_facet_bindings binding
453
+ WHERE binding.id = NEW.binding_id
454
+ AND binding.account_id = NEW.account_id
455
+ AND binding.workspace_id = NEW.workspace_id
456
+ ) THEN
457
+ RAISE EXCEPTION 'Facet binding owner does not match its binding tenant'
458
+ USING ERRCODE = '23514';
459
+ END IF;
460
+ IF NEW.owner_kind = 'plugin' THEN
461
+ IF NEW.owner_id !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' THEN
462
+ RAISE EXCEPTION 'Plugin Facet binding owner belongs to another tenant or does not exist'
463
+ USING ERRCODE = '23514';
464
+ ELSIF NOT EXISTS (
465
+ SELECT 1 FROM capability_plugin_installations plugin
466
+ WHERE plugin.id = NEW.owner_id::uuid
467
+ AND plugin.account_id = NEW.account_id
468
+ AND plugin.workspace_id = NEW.workspace_id
469
+ ) THEN
470
+ RAISE EXCEPTION 'Plugin Facet binding owner belongs to another tenant or does not exist'
471
+ USING ERRCODE = '23514';
472
+ END IF;
473
+ ELSIF NEW.owner_kind = 'pack'
474
+ AND NEW.owner_id ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
475
+ THEN
476
+ IF NOT EXISTS (
477
+ SELECT 1 FROM pack_installations pack
478
+ WHERE pack.id = NEW.owner_id::uuid
479
+ AND pack.account_id = NEW.account_id
480
+ AND pack.workspace_id = NEW.workspace_id
481
+ ) THEN
482
+ RAISE EXCEPTION 'Pack Facet binding owner belongs to another tenant or does not exist'
483
+ USING ERRCODE = '23514';
484
+ END IF;
485
+ END IF;
486
+ RETURN NEW;
487
+ END;
488
+ $$;
489
+
490
+ CREATE TRIGGER integration_facet_binding_owners_validate
491
+ BEFORE INSERT OR UPDATE ON integration_facet_binding_owners
492
+ FOR EACH ROW EXECUTE FUNCTION capability_v2_validate_facet_binding_owner();
493
+
494
+ DO $facet_postcondition$
495
+ BEGIN
496
+ IF to_regclass('integration_feature_facets') IS NOT NULL
497
+ OR to_regclass('integration_feature_bindings') IS NOT NULL
498
+ OR to_regclass('integration_feature_binding_owners') IS NOT NULL
499
+ OR to_regclass('integration_facet_definitions') IS NULL
500
+ OR to_regclass('integration_facet_bindings') IS NULL
501
+ OR to_regclass('integration_facet_binding_owners') IS NULL
502
+ THEN
503
+ RAISE EXCEPTION 'Integration Facet physical authority migration did not converge'
504
+ USING ERRCODE = '23514';
505
+ END IF;
506
+
507
+ IF EXISTS (
508
+ SELECT 1
509
+ FROM information_schema.columns
510
+ WHERE table_schema = current_schema()
511
+ AND table_name IN (
512
+ 'integration_facet_definitions',
513
+ 'integration_facet_bindings',
514
+ 'integration_facet_binding_owners'
515
+ )
516
+ AND column_name LIKE '%feature%'
517
+ ) OR EXISTS (
518
+ SELECT 1
519
+ FROM pg_constraint
520
+ WHERE conrelid IN (
521
+ to_regclass('integration_facet_definitions'),
522
+ to_regclass('integration_facet_bindings'),
523
+ to_regclass('integration_facet_binding_owners')
524
+ )
525
+ AND conname LIKE '%feature%'
526
+ ) OR EXISTS (
527
+ SELECT 1
528
+ FROM pg_indexes
529
+ WHERE schemaname = current_schema()
530
+ AND tablename IN (
531
+ 'integration_facet_definitions',
532
+ 'integration_facet_bindings',
533
+ 'integration_facet_binding_owners'
534
+ )
535
+ AND indexname LIKE '%feature%'
536
+ ) OR EXISTS (
537
+ SELECT 1
538
+ FROM information_schema.triggers
539
+ WHERE trigger_schema = current_schema()
540
+ AND event_object_table IN (
541
+ 'integration_facet_definitions',
542
+ 'integration_facet_bindings',
543
+ 'integration_facet_binding_owners'
544
+ )
545
+ AND trigger_name LIKE '%feature%'
546
+ ) OR EXISTS (
547
+ SELECT 1
548
+ FROM pg_proc procedure
549
+ JOIN pg_namespace namespace ON namespace.oid = procedure.pronamespace
550
+ WHERE namespace.nspname = current_schema()
551
+ AND procedure.proname LIKE '%feature_binding%'
552
+ ) THEN
553
+ RAISE EXCEPTION 'Integration Facet catalog names retain legacy Feature identity'
554
+ USING ERRCODE = '23514';
555
+ END IF;
556
+
557
+ IF EXISTS (
558
+ SELECT 1
559
+ FROM workspace_packs pack,
560
+ LATERAL jsonb_array_elements(COALESCE(pack.manifest -> 'components', '[]'::jsonb)) component
561
+ WHERE component ->> 'kind' = 'feature'
562
+ OR component ? 'featureKey'
563
+ ) OR EXISTS (
564
+ SELECT 1
565
+ FROM pack_installations installation,
566
+ LATERAL jsonb_array_elements(COALESCE(installation.manifest_snapshot -> 'components', '[]'::jsonb)) component
567
+ WHERE installation.manifest_snapshot IS NOT NULL
568
+ AND (component ->> 'kind' = 'feature' OR component ? 'featureKey')
569
+ ) OR EXISTS (
570
+ SELECT 1 FROM pack_installation_components
571
+ WHERE kind = 'feature' OR metadata ? 'featureKey'
572
+ ) OR EXISTS (
573
+ SELECT 1 FROM integration_facet_binding_owners
574
+ WHERE owner_kind = 'direct' AND owner_id LIKE 'feature:%'
575
+ ) OR EXISTS (
576
+ SELECT 1 FROM capability_operations
577
+ WHERE target_kind = 'facet_binding'
578
+ AND result IS NOT NULL
579
+ AND (
580
+ result ? 'featureKey'
581
+ OR (jsonb_typeof(result -> 'binding') = 'object' AND result -> 'binding' ? 'featureKey')
582
+ )
583
+ ) THEN
584
+ RAISE EXCEPTION 'Integration Facet persisted identity migration did not converge'
585
+ USING ERRCODE = '23514';
586
+ END IF;
587
+
588
+ IF EXISTS (
589
+ SELECT 1
590
+ FROM pack_installations
591
+ WHERE manifest_snapshot IS NOT NULL
592
+ AND manifest_digest <> encode(
593
+ digest(
594
+ convert_to(
595
+ opengeni_private.integration_facet_canonical_json(manifest_snapshot),
596
+ 'UTF8'
597
+ ),
598
+ 'sha256'
599
+ ),
600
+ 'hex'
601
+ )
602
+ ) THEN
603
+ RAISE EXCEPTION 'Integration Facet Pack digest migration did not converge'
604
+ USING ERRCODE = '23514';
605
+ END IF;
606
+ END
607
+ $facet_postcondition$;
608
+
609
+ DROP FUNCTION opengeni_private.integration_facet_rewrite_pack(jsonb);
610
+ DROP FUNCTION opengeni_private.integration_facet_canonical_json(jsonb);