@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,1363 @@
1
+ -- deployment-mode: rolling
2
+ -- Activate an atomic, server-authoritative visibility transition. Independent
3
+ -- fork activation is added by the same numbered slice after this checkpoint.
4
+
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '10min';
7
+
8
+ ALTER TABLE "session_attempt_interruptions"
9
+ DROP CONSTRAINT "session_attempt_interruptions_kind_check";
10
+ ALTER TABLE "session_attempt_interruptions"
11
+ ADD CONSTRAINT "session_attempt_interruptions_kind_check"
12
+ CHECK ("kind" IN (
13
+ 'session_pause', 'workspace_pause', 'steer', 'maintenance', 'authority_change'
14
+ )) NOT VALID;
15
+ ALTER TABLE "session_attempt_interruptions"
16
+ VALIDATE CONSTRAINT "session_attempt_interruptions_kind_check";
17
+
18
+ ALTER TABLE "sessions"
19
+ ADD COLUMN "owner_subject_id" text;
20
+
21
+ UPDATE "sessions" session_row
22
+ SET "owner_subject_id" = membership."subject_id"
23
+ FROM "organization_memberships" membership
24
+ WHERE session_row."owner_organization_membership_id" = membership."id"
25
+ AND session_row."account_id" = membership."account_id"
26
+ AND session_row."owner_subject_id" IS NULL;
27
+
28
+ ALTER TABLE "sessions"
29
+ ADD CONSTRAINT "sessions_owner_subject_pair_check" CHECK (
30
+ ("owner_organization_membership_id" IS NULL) = ("owner_subject_id" IS NULL)
31
+ ) NOT VALID,
32
+ ADD CONSTRAINT "sessions_owner_subject_account_fk"
33
+ FOREIGN KEY ("account_id", "owner_subject_id")
34
+ REFERENCES "organization_memberships" ("account_id", "subject_id")
35
+ ON DELETE RESTRICT NOT VALID;
36
+ ALTER TABLE "sessions"
37
+ VALIDATE CONSTRAINT "sessions_owner_subject_pair_check",
38
+ VALIDATE CONSTRAINT "sessions_owner_subject_account_fk";
39
+
40
+ DROP POLICY IF EXISTS organization_tenancy_lifecycle ON "organization_memberships";
41
+ CREATE POLICY organization_tenancy_lifecycle ON "organization_memberships"
42
+ USING (
43
+ current_setting('opengeni.organization_tenancy_lifecycle', true)
44
+ IN ('managed_human_provisioning', 'session_visibility_activation')
45
+ )
46
+ WITH CHECK (
47
+ current_setting('opengeni.organization_tenancy_lifecycle', true)
48
+ IN ('managed_human_provisioning', 'session_visibility_activation')
49
+ );
50
+
51
+ DROP POLICY IF EXISTS organization_tenancy_lifecycle ON "organization_user_resource_grants";
52
+ CREATE POLICY organization_tenancy_lifecycle ON "organization_user_resource_grants"
53
+ USING (
54
+ current_setting('opengeni.organization_tenancy_lifecycle', true)
55
+ IN ('managed_human_provisioning', 'session_visibility_activation')
56
+ )
57
+ WITH CHECK (
58
+ current_setting('opengeni.organization_tenancy_lifecycle', true)
59
+ IN ('managed_human_provisioning', 'session_visibility_activation')
60
+ );
61
+
62
+ DO $session_visibility_write_fence$
63
+ DECLARE
64
+ data_schema text := current_schema();
65
+ migration_owner text := current_user;
66
+ BEGIN
67
+ EXECUTE format(
68
+ 'CREATE TABLE IF NOT EXISTS %I.session_visibility_write_capabilities ('
69
+ || 'backend_pid integer NOT NULL, '
70
+ || 'transaction_id xid8 NOT NULL, '
71
+ || 'capability_id uuid NOT NULL, '
72
+ || 'PRIMARY KEY (backend_pid, transaction_id, capability_id)'
73
+ || ')',
74
+ data_schema
75
+ );
76
+ EXECUTE format(
77
+ 'ALTER TABLE %I.session_visibility_write_capabilities '
78
+ || 'ENABLE ROW LEVEL SECURITY',
79
+ data_schema
80
+ );
81
+ EXECUTE format(
82
+ 'ALTER TABLE %I.session_visibility_write_capabilities '
83
+ || 'FORCE ROW LEVEL SECURITY',
84
+ data_schema
85
+ );
86
+ EXECUTE format(
87
+ 'DROP POLICY IF EXISTS session_visibility_capability_owner '
88
+ || 'ON %I.session_visibility_write_capabilities',
89
+ data_schema
90
+ );
91
+ EXECUTE format(
92
+ 'CREATE POLICY session_visibility_capability_owner '
93
+ || 'ON %I.session_visibility_write_capabilities '
94
+ || 'FOR ALL USING (current_user = %L) WITH CHECK (current_user = %L)',
95
+ data_schema,
96
+ migration_owner,
97
+ migration_owner
98
+ );
99
+ EXECUTE format(
100
+ 'REVOKE ALL ON %I.session_visibility_write_capabilities FROM PUBLIC',
101
+ data_schema
102
+ );
103
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
104
+ EXECUTE format(
105
+ 'REVOKE ALL ON %I.session_visibility_write_capabilities FROM opengeni_app',
106
+ data_schema
107
+ );
108
+ END IF;
109
+
110
+ EXECUTE format($ddl$
111
+ CREATE OR REPLACE FUNCTION %1$I.guard_session_authority_write()
112
+ RETURNS trigger
113
+ LANGUAGE plpgsql
114
+ SECURITY DEFINER
115
+ SET search_path = %1$I, pg_catalog
116
+ AS $body$
117
+ DECLARE
118
+ resolved_owner_id uuid;
119
+ resolved_owner_subject_id text;
120
+ owner_provenance_supplied boolean :=
121
+ NEW.owner_organization_membership_id IS NOT NULL
122
+ OR NEW.owner_subject_id IS NOT NULL;
123
+ previous_lifecycle_marker text := pg_catalog.current_setting(
124
+ 'opengeni.organization_tenancy_lifecycle', true
125
+ );
126
+ BEGIN
127
+ IF TG_OP = 'INSERT'
128
+ AND NEW.owner_organization_membership_id IS NULL
129
+ AND NEW.owner_subject_id IS NULL
130
+ THEN
131
+ IF NEW.parent_session_id IS NOT NULL THEN
132
+ SELECT parent.owner_organization_membership_id, parent.owner_subject_id
133
+ INTO resolved_owner_id, resolved_owner_subject_id
134
+ FROM sessions parent
135
+ WHERE parent.account_id = NEW.account_id
136
+ AND parent.workspace_id = NEW.workspace_id
137
+ AND parent.id = NEW.parent_session_id;
138
+ ELSIF NEW.created_by_kind = 'subject'
139
+ AND NEW.created_by_subject_id IS NOT NULL
140
+ THEN
141
+ PERFORM pg_catalog.set_config(
142
+ 'opengeni.organization_tenancy_lifecycle',
143
+ 'session_visibility_activation',
144
+ true
145
+ );
146
+ SELECT membership.id, membership.subject_id
147
+ INTO resolved_owner_id, resolved_owner_subject_id
148
+ FROM organization_memberships membership
149
+ WHERE membership.account_id = NEW.account_id
150
+ AND membership.subject_id = NEW.created_by_subject_id
151
+ AND membership.status = 'active'
152
+ AND EXISTS (
153
+ SELECT 1
154
+ FROM workspace_memberships workspace_membership
155
+ WHERE workspace_membership.account_id = NEW.account_id
156
+ AND workspace_membership.workspace_id = NEW.workspace_id
157
+ AND workspace_membership.subject_id = membership.subject_id
158
+ );
159
+ END IF;
160
+ NEW.owner_organization_membership_id := resolved_owner_id;
161
+ NEW.owner_subject_id := resolved_owner_subject_id;
162
+ PERFORM pg_catalog.set_config(
163
+ 'opengeni.organization_tenancy_lifecycle',
164
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
165
+ true
166
+ );
167
+ END IF;
168
+
169
+ IF (NEW.owner_organization_membership_id IS NULL)
170
+ <> (NEW.owner_subject_id IS NULL)
171
+ OR (NEW.visibility = 'user_private'
172
+ AND NEW.owner_organization_membership_id IS NULL)
173
+ THEN
174
+ RAISE EXCEPTION 'session authority owner provenance is incomplete'
175
+ USING ERRCODE = '23514';
176
+ END IF;
177
+
178
+ IF TG_OP = 'UPDATE'
179
+ AND (
180
+ NEW.visibility IS DISTINCT FROM OLD.visibility
181
+ OR NEW.owner_organization_membership_id
182
+ IS DISTINCT FROM OLD.owner_organization_membership_id
183
+ OR NEW.owner_subject_id IS DISTINCT FROM OLD.owner_subject_id
184
+ OR NEW.authority_epoch IS DISTINCT FROM OLD.authority_epoch
185
+ OR NEW.forked_from_session_id IS DISTINCT FROM OLD.forked_from_session_id
186
+ OR NEW.forked_from_authority_epoch
187
+ IS DISTINCT FROM OLD.forked_from_authority_epoch
188
+ OR NEW.forked_from_visibility IS DISTINCT FROM OLD.forked_from_visibility
189
+ OR NEW.forked_at IS DISTINCT FROM OLD.forked_at
190
+ OR NEW.forked_by_organization_membership_id
191
+ IS DISTINCT FROM OLD.forked_by_organization_membership_id
192
+ )
193
+ AND NOT EXISTS (
194
+ SELECT 1
195
+ FROM session_visibility_write_capabilities capability
196
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
197
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id()
198
+ AND capability.capability_id = nullif(
199
+ pg_catalog.current_setting(
200
+ 'opengeni.session_visibility_write_capability', true
201
+ ),
202
+ ''
203
+ )::uuid
204
+ )
205
+ THEN
206
+ RAISE EXCEPTION 'session authority changes require the lifecycle capability'
207
+ USING ERRCODE = '42501';
208
+ END IF;
209
+
210
+ IF TG_OP = 'INSERT'
211
+ AND (
212
+ NEW.visibility <> 'workspace_shared'
213
+ OR NEW.authority_epoch <> 1
214
+ OR NEW.forked_from_session_id IS NOT NULL
215
+ OR NEW.forked_from_authority_epoch IS NOT NULL
216
+ OR NEW.forked_from_visibility IS NOT NULL
217
+ OR NEW.forked_at IS NOT NULL
218
+ OR NEW.forked_by_organization_membership_id IS NOT NULL
219
+ OR owner_provenance_supplied
220
+ )
221
+ AND NOT EXISTS (
222
+ SELECT 1
223
+ FROM session_visibility_write_capabilities capability
224
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
225
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id()
226
+ AND capability.capability_id = nullif(
227
+ pg_catalog.current_setting(
228
+ 'opengeni.session_visibility_write_capability', true
229
+ ),
230
+ ''
231
+ )::uuid
232
+ )
233
+ THEN
234
+ RAISE EXCEPTION 'non-default session authority requires the lifecycle capability'
235
+ USING ERRCODE = '42501';
236
+ END IF;
237
+ RETURN NEW;
238
+ EXCEPTION WHEN OTHERS THEN
239
+ PERFORM pg_catalog.set_config(
240
+ 'opengeni.organization_tenancy_lifecycle',
241
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
242
+ true
243
+ );
244
+ RAISE;
245
+ END;
246
+ $body$;
247
+ $ddl$, data_schema);
248
+ EXECUTE format(
249
+ 'DROP TRIGGER IF EXISTS sessions_authority_write_fence ON %I.sessions',
250
+ data_schema
251
+ );
252
+ EXECUTE format(
253
+ 'CREATE TRIGGER sessions_authority_write_fence '
254
+ || 'BEFORE INSERT OR UPDATE ON %I.sessions '
255
+ || 'FOR EACH ROW EXECUTE FUNCTION %I.guard_session_authority_write()',
256
+ data_schema,
257
+ data_schema
258
+ );
259
+ END
260
+ $session_visibility_write_fence$;
261
+
262
+ DO $session_visibility_rls$
263
+ DECLARE
264
+ data_schema text := current_schema();
265
+ target record;
266
+ policy_expression text;
267
+ manual_table text;
268
+ BEGIN
269
+ EXECUTE format($ddl$
270
+ CREATE OR REPLACE FUNCTION %1$I.session_private_actor_visible(
271
+ p_account_id uuid,
272
+ p_workspace_id uuid,
273
+ p_owner_organization_membership_id uuid,
274
+ p_owner_subject_id text
275
+ ) RETURNS boolean
276
+ LANGUAGE plpgsql
277
+ STABLE
278
+ SECURITY DEFINER
279
+ SET search_path = %1$I, pg_catalog
280
+ AS $body$
281
+ DECLARE
282
+ actor_subject_id text := nullif(
283
+ pg_catalog.current_setting('opengeni.subject_id', true), ''
284
+ );
285
+ initiating_human_subject_id text := nullif(
286
+ pg_catalog.current_setting('opengeni.initiating_human_subject_id', true), ''
287
+ );
288
+ previous_lifecycle_marker text := pg_catalog.current_setting(
289
+ 'opengeni.organization_tenancy_lifecycle', true
290
+ );
291
+ visible boolean := false;
292
+ BEGIN
293
+ IF p_account_id IS NULL
294
+ OR p_workspace_id IS NULL
295
+ OR p_owner_organization_membership_id IS NULL
296
+ OR p_owner_subject_id IS NULL
297
+ OR (
298
+ actor_subject_id IS DISTINCT FROM p_owner_subject_id
299
+ AND initiating_human_subject_id IS DISTINCT FROM p_owner_subject_id
300
+ )
301
+ THEN
302
+ RETURN false;
303
+ END IF;
304
+ PERFORM pg_catalog.set_config(
305
+ 'opengeni.organization_tenancy_lifecycle',
306
+ 'session_visibility_activation',
307
+ true
308
+ );
309
+ SELECT EXISTS (
310
+ SELECT 1
311
+ FROM organization_memberships membership
312
+ WHERE membership.account_id = p_account_id
313
+ AND membership.id = p_owner_organization_membership_id
314
+ AND membership.subject_id = p_owner_subject_id
315
+ AND membership.status = 'active'
316
+ AND EXISTS (
317
+ SELECT 1
318
+ FROM workspace_memberships workspace_membership
319
+ WHERE workspace_membership.account_id = p_account_id
320
+ AND workspace_membership.workspace_id = p_workspace_id
321
+ AND workspace_membership.subject_id = p_owner_subject_id
322
+ )
323
+ ) INTO visible;
324
+ PERFORM pg_catalog.set_config(
325
+ 'opengeni.organization_tenancy_lifecycle',
326
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
327
+ true
328
+ );
329
+ RETURN visible;
330
+ EXCEPTION WHEN OTHERS THEN
331
+ PERFORM pg_catalog.set_config(
332
+ 'opengeni.organization_tenancy_lifecycle',
333
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
334
+ true
335
+ );
336
+ RAISE;
337
+ END;
338
+ $body$;
339
+ $ddl$, data_schema);
340
+ EXECUTE format(
341
+ 'REVOKE ALL ON FUNCTION %I.session_private_actor_visible(uuid,uuid,uuid,text) FROM PUBLIC',
342
+ data_schema
343
+ );
344
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
345
+ EXECUTE format(
346
+ 'GRANT EXECUTE ON FUNCTION %I.session_private_actor_visible(uuid,uuid,uuid,text) TO opengeni_app',
347
+ data_schema
348
+ );
349
+ END IF;
350
+
351
+ EXECUTE format($ddl$
352
+ CREATE OR REPLACE FUNCTION %1$I.session_reference_visible(
353
+ p_account_id uuid,
354
+ p_workspace_id uuid,
355
+ p_session_id uuid
356
+ ) RETURNS boolean
357
+ LANGUAGE sql
358
+ STABLE
359
+ SECURITY INVOKER
360
+ SET search_path = %1$I, pg_catalog
361
+ AS $body$
362
+ SELECT p_session_id IS NULL OR EXISTS (
363
+ SELECT 1
364
+ FROM sessions session_row
365
+ WHERE session_row.account_id = p_account_id
366
+ AND session_row.workspace_id = p_workspace_id
367
+ AND session_row.id = p_session_id
368
+ )
369
+ $body$;
370
+ $ddl$, data_schema);
371
+
372
+ EXECUTE format('DROP POLICY IF EXISTS session_visibility_isolation ON %I.sessions', data_schema);
373
+ EXECUTE format(
374
+ 'CREATE POLICY session_visibility_isolation ON %1$I.sessions AS RESTRICTIVE '
375
+ || 'FOR ALL USING ('
376
+ || 'nullif(pg_catalog.current_setting(''opengeni.subject_id'', true), '''') IS NULL '
377
+ || 'OR visibility = ''workspace_shared'' '
378
+ || 'OR %1$I.session_private_actor_visible('
379
+ || 'account_id, workspace_id, owner_organization_membership_id, owner_subject_id)'
380
+ || ') WITH CHECK ('
381
+ || 'nullif(pg_catalog.current_setting(''opengeni.subject_id'', true), '''') IS NULL '
382
+ || 'OR visibility = ''workspace_shared'' '
383
+ || 'OR %1$I.session_private_actor_visible('
384
+ || 'account_id, workspace_id, owner_organization_membership_id, owner_subject_id)'
385
+ || ')',
386
+ data_schema
387
+ );
388
+
389
+ FOR target IN
390
+ SELECT
391
+ relation.relname AS table_name,
392
+ pg_catalog.string_agg(
393
+ pg_catalog.format(
394
+ '%1$I.session_reference_visible(account_id, workspace_id, %2$I)',
395
+ data_schema,
396
+ attribute.attname
397
+ ),
398
+ ' AND ' ORDER BY attribute.attname
399
+ ) AS expression
400
+ FROM pg_catalog.pg_constraint constraint_row
401
+ JOIN pg_catalog.pg_class relation
402
+ ON relation.oid = constraint_row.conrelid
403
+ JOIN pg_catalog.pg_namespace relation_namespace
404
+ ON relation_namespace.oid = relation.relnamespace
405
+ JOIN pg_catalog.pg_class referenced_relation
406
+ ON referenced_relation.oid = constraint_row.confrelid
407
+ JOIN pg_catalog.pg_namespace referenced_namespace
408
+ ON referenced_namespace.oid = referenced_relation.relnamespace
409
+ JOIN LATERAL pg_catalog.unnest(constraint_row.conkey)
410
+ WITH ORDINALITY source_key(attnum, ordinality) ON true
411
+ JOIN LATERAL pg_catalog.unnest(constraint_row.confkey)
412
+ WITH ORDINALITY referenced_key(attnum, ordinality)
413
+ ON referenced_key.ordinality = source_key.ordinality
414
+ JOIN pg_catalog.pg_attribute attribute
415
+ ON attribute.attrelid = relation.oid
416
+ AND attribute.attnum = source_key.attnum
417
+ JOIN pg_catalog.pg_attribute referenced_attribute
418
+ ON referenced_attribute.attrelid = referenced_relation.oid
419
+ AND referenced_attribute.attnum = referenced_key.attnum
420
+ WHERE constraint_row.contype = 'f'
421
+ AND relation_namespace.nspname = data_schema
422
+ AND referenced_namespace.nspname = data_schema
423
+ AND referenced_relation.relname = 'sessions'
424
+ AND referenced_attribute.attname = 'id'
425
+ AND relation.relname <> 'sessions'
426
+ AND relation.relrowsecurity
427
+ AND EXISTS (
428
+ SELECT 1 FROM pg_catalog.pg_attribute account_column
429
+ WHERE account_column.attrelid = relation.oid
430
+ AND account_column.attname = 'account_id'
431
+ AND NOT account_column.attisdropped
432
+ )
433
+ AND EXISTS (
434
+ SELECT 1 FROM pg_catalog.pg_attribute workspace_column
435
+ WHERE workspace_column.attrelid = relation.oid
436
+ AND workspace_column.attname = 'workspace_id'
437
+ AND NOT workspace_column.attisdropped
438
+ )
439
+ GROUP BY relation.relname
440
+ LOOP
441
+ EXECUTE format(
442
+ 'DROP POLICY IF EXISTS session_visibility_isolation ON %I.%I',
443
+ data_schema,
444
+ target.table_name
445
+ );
446
+ EXECUTE format(
447
+ 'CREATE POLICY session_visibility_isolation ON %I.%I AS RESTRICTIVE '
448
+ || 'FOR ALL USING (%s) WITH CHECK (%s)',
449
+ data_schema,
450
+ target.table_name,
451
+ target.expression,
452
+ target.expression
453
+ );
454
+ END LOOP;
455
+
456
+ FOREACH manual_table IN ARRAY ARRAY[
457
+ 'connector_action_requests',
458
+ 'memory_slack_publications',
459
+ 'model_call_facts',
460
+ 'session_attempt_codemode_calls',
461
+ 'session_attempt_tool_catalogs',
462
+ 'session_human_input_requests',
463
+ 'usage_events'
464
+ ]
465
+ LOOP
466
+ IF pg_catalog.to_regclass(pg_catalog.format('%I.%I', data_schema, manual_table)) IS NULL THEN
467
+ CONTINUE;
468
+ END IF;
469
+ policy_expression := format(
470
+ '%I.session_reference_visible(account_id, workspace_id, session_id)',
471
+ data_schema
472
+ );
473
+ EXECUTE format(
474
+ 'DROP POLICY IF EXISTS session_visibility_isolation ON %I.%I',
475
+ data_schema,
476
+ manual_table
477
+ );
478
+ EXECUTE format(
479
+ 'CREATE POLICY session_visibility_isolation ON %I.%I AS RESTRICTIVE '
480
+ || 'FOR ALL USING (%s) WITH CHECK (%s)',
481
+ data_schema,
482
+ manual_table,
483
+ policy_expression,
484
+ policy_expression
485
+ );
486
+ END LOOP;
487
+ END
488
+ $session_visibility_rls$;
489
+
490
+ DO $session_visibility_activation$
491
+ DECLARE
492
+ data_schema text := current_schema();
493
+ BEGIN
494
+ EXECUTE format($ddl$
495
+ CREATE OR REPLACE FUNCTION %1$I.transition_session_visibility(
496
+ p_account_id uuid,
497
+ p_workspace_id uuid,
498
+ p_session_id uuid,
499
+ p_actor_subject_id text,
500
+ p_target_visibility text,
501
+ p_expected_authority_epoch integer,
502
+ p_operation_key text,
503
+ p_canonical_request_hash text
504
+ )
505
+ RETURNS TABLE (
506
+ operation_id uuid,
507
+ visibility text,
508
+ authority_epoch integer,
509
+ owner_organization_membership_id uuid,
510
+ changed boolean,
511
+ replay boolean,
512
+ interrupted_attempt_count integer,
513
+ cancelled_turn_count integer,
514
+ cancelled_update_count integer,
515
+ paused_goal_count integer,
516
+ revoked_grant_count integer
517
+ )
518
+ LANGUAGE plpgsql
519
+ SECURITY DEFINER
520
+ SET search_path = %1$I, pg_catalog
521
+ AS $body$
522
+ DECLARE
523
+ actor_membership organization_memberships%%ROWTYPE;
524
+ session_row sessions%%ROWTYPE;
525
+ receipt_row session_command_receipts%%ROWTYPE;
526
+ new_owner_id uuid;
527
+ new_epoch integer;
528
+ event_sequence integer;
529
+ interruption_count integer := 0;
530
+ turn_count integer := 0;
531
+ update_count integer := 0;
532
+ goal_count integer := 0;
533
+ grant_count integer := 0;
534
+ result_payload jsonb;
535
+ visibility_write_capability_id uuid := pg_catalog.gen_random_uuid();
536
+ previous_visibility_write_capability text := pg_catalog.current_setting(
537
+ 'opengeni.session_visibility_write_capability', true
538
+ );
539
+ previous_lifecycle_marker text := pg_catalog.current_setting(
540
+ 'opengeni.organization_tenancy_lifecycle', true
541
+ );
542
+ BEGIN
543
+ IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_session_id IS NULL
544
+ OR p_actor_subject_id IS NULL OR p_expected_authority_epoch IS NULL
545
+ OR p_operation_key IS NULL OR p_canonical_request_hash IS NULL
546
+ THEN
547
+ RAISE EXCEPTION 'session visibility transition requires complete authority'
548
+ USING ERRCODE = '42501';
549
+ END IF;
550
+ IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
551
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
552
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
553
+ THEN
554
+ RAISE EXCEPTION 'session visibility transition authority is invalid'
555
+ USING ERRCODE = '42501';
556
+ END IF;
557
+ IF p_target_visibility NOT IN ('user_private', 'workspace_shared')
558
+ OR p_expected_authority_epoch < 1
559
+ OR p_actor_subject_id <> pg_catalog.btrim(p_actor_subject_id)
560
+ OR pg_catalog.length(p_actor_subject_id) NOT BETWEEN 1 AND 1024
561
+ OR p_operation_key <> pg_catalog.btrim(p_operation_key)
562
+ OR pg_catalog.length(p_operation_key) NOT BETWEEN 1 AND 1024
563
+ OR p_canonical_request_hash !~ '^[0-9a-f]{64}$'
564
+ THEN
565
+ RAISE EXCEPTION 'session visibility transition request is invalid'
566
+ USING ERRCODE = '22023';
567
+ END IF;
568
+
569
+ PERFORM pg_catalog.set_config(
570
+ 'opengeni.organization_tenancy_lifecycle',
571
+ 'session_visibility_activation',
572
+ true
573
+ );
574
+
575
+ PERFORM 1 FROM workspaces workspace_row
576
+ WHERE workspace_row.id = p_workspace_id
577
+ AND workspace_row.account_id = p_account_id
578
+ FOR UPDATE;
579
+ IF NOT FOUND THEN
580
+ RAISE EXCEPTION 'session visibility transition workspace is unavailable'
581
+ USING ERRCODE = '42501';
582
+ END IF;
583
+
584
+ SELECT membership.* INTO actor_membership
585
+ FROM organization_memberships membership
586
+ WHERE membership.account_id = p_account_id
587
+ AND membership.subject_id = p_actor_subject_id
588
+ AND membership.status = 'active'
589
+ FOR UPDATE;
590
+ IF NOT FOUND OR NOT EXISTS (
591
+ SELECT 1 FROM workspace_memberships workspace_membership
592
+ WHERE workspace_membership.account_id = p_account_id
593
+ AND workspace_membership.workspace_id = p_workspace_id
594
+ AND workspace_membership.subject_id = p_actor_subject_id
595
+ ) THEN
596
+ RAISE EXCEPTION 'session visibility transition requires active membership'
597
+ USING ERRCODE = '42501';
598
+ END IF;
599
+
600
+ SELECT session.* INTO session_row
601
+ FROM sessions session
602
+ WHERE session.account_id = p_account_id
603
+ AND session.workspace_id = p_workspace_id
604
+ AND session.id = p_session_id
605
+ FOR UPDATE;
606
+ IF NOT FOUND THEN
607
+ RAISE EXCEPTION 'session visibility transition session is unavailable'
608
+ USING ERRCODE = 'P0002';
609
+ END IF;
610
+ IF session_row.owner_organization_membership_id IS NOT NULL
611
+ AND session_row.owner_organization_membership_id <> actor_membership.id
612
+ THEN
613
+ RAISE EXCEPTION 'session visibility transition is owner-only'
614
+ USING ERRCODE = '42501';
615
+ END IF;
616
+ IF session_row.owner_organization_membership_id IS NULL
617
+ OR session_row.owner_subject_id IS NULL
618
+ THEN
619
+ RAISE EXCEPTION 'legacy workspace session has no transferable owner authority'
620
+ USING ERRCODE = '42501';
621
+ END IF;
622
+
623
+ INSERT INTO session_command_receipts (
624
+ account_id, workspace_id, actor_type, actor_subject_id, action,
625
+ target_session_id, operation_key, canonical_request_hash
626
+ ) VALUES (
627
+ p_account_id, p_workspace_id, 'human', p_actor_subject_id,
628
+ 'session.visibility.change', p_session_id, p_operation_key,
629
+ p_canonical_request_hash
630
+ ) ON CONFLICT DO NOTHING;
631
+
632
+ SELECT receipt.* INTO receipt_row
633
+ FROM session_command_receipts receipt
634
+ WHERE receipt.workspace_id = p_workspace_id
635
+ AND receipt.actor_type = 'human'
636
+ AND receipt.actor_subject_id = p_actor_subject_id
637
+ AND receipt.actor_attempt_id IS NULL
638
+ AND receipt.action = 'session.visibility.change'
639
+ AND receipt.target_session_id = p_session_id
640
+ AND receipt.target_turn_id IS NULL
641
+ AND receipt.operation_key = p_operation_key
642
+ FOR UPDATE;
643
+ IF NOT FOUND THEN
644
+ RAISE EXCEPTION 'session visibility transition receipt is unavailable'
645
+ USING ERRCODE = 'P0002';
646
+ END IF;
647
+ IF receipt_row.canonical_request_hash <> p_canonical_request_hash THEN
648
+ RAISE EXCEPTION 'session visibility transition idempotency conflict'
649
+ USING ERRCODE = '23505';
650
+ END IF;
651
+ IF receipt_row.result ->> 'status' = 'applied' THEN
652
+ operation_id := receipt_row.id;
653
+ visibility := receipt_row.result ->> 'visibility';
654
+ authority_epoch := (receipt_row.result ->> 'authorityEpoch')::integer;
655
+ owner_organization_membership_id := session_row.owner_organization_membership_id;
656
+ changed := (receipt_row.result ->> 'changed')::boolean;
657
+ replay := true;
658
+ interrupted_attempt_count := (receipt_row.result ->> 'interruptedAttemptCount')::integer;
659
+ cancelled_turn_count := (receipt_row.result ->> 'cancelledTurnCount')::integer;
660
+ cancelled_update_count := (receipt_row.result ->> 'cancelledUpdateCount')::integer;
661
+ paused_goal_count := (receipt_row.result ->> 'pausedGoalCount')::integer;
662
+ revoked_grant_count := (receipt_row.result ->> 'revokedGrantCount')::integer;
663
+ PERFORM pg_catalog.set_config(
664
+ 'opengeni.organization_tenancy_lifecycle',
665
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
666
+ true
667
+ );
668
+ RETURN NEXT;
669
+ RETURN;
670
+ END IF;
671
+ IF session_row.authority_epoch <> p_expected_authority_epoch THEN
672
+ RAISE EXCEPTION 'session visibility transition authority epoch conflict'
673
+ USING ERRCODE = '40001';
674
+ END IF;
675
+
676
+ new_owner_id := session_row.owner_organization_membership_id;
677
+ IF session_row.visibility = p_target_visibility THEN
678
+ new_epoch := session_row.authority_epoch;
679
+ ELSE
680
+ new_epoch := session_row.authority_epoch + 1;
681
+ IF new_epoch < 2 THEN
682
+ RAISE EXCEPTION 'session visibility transition authority epoch exhausted'
683
+ USING ERRCODE = '22003';
684
+ END IF;
685
+
686
+ INSERT INTO session_attempt_interruptions (
687
+ account_id, workspace_id, session_id, operation_id, attempt_id,
688
+ kind, control_revision
689
+ )
690
+ SELECT attempt.account_id, attempt.workspace_id, attempt.session_id,
691
+ receipt_row.id, attempt.id, 'authority_change', new_epoch
692
+ FROM session_turn_attempts attempt
693
+ WHERE attempt.workspace_id = p_workspace_id
694
+ AND attempt.session_id = p_session_id
695
+ AND attempt.state IN ('claimed', 'running')
696
+ ON CONFLICT ON CONSTRAINT session_attempt_interruptions_operation_attempt_uq
697
+ DO NOTHING;
698
+ GET DIAGNOSTICS interruption_count = ROW_COUNT;
699
+
700
+ UPDATE session_turns turn_row
701
+ SET status = 'cancelled', cancelled_by = p_actor_subject_id,
702
+ cancel_reason = 'authority_changed', finished_at = pg_catalog.clock_timestamp(),
703
+ updated_at = pg_catalog.clock_timestamp(), version = turn_row.version + 1
704
+ WHERE turn_row.workspace_id = p_workspace_id
705
+ AND turn_row.session_id = p_session_id
706
+ AND turn_row.status = 'queued';
707
+ GET DIAGNOSTICS turn_count = ROW_COUNT;
708
+
709
+ UPDATE session_system_updates update_row
710
+ SET state = 'cancelled'
711
+ WHERE update_row.workspace_id = p_workspace_id
712
+ AND update_row.session_id = p_session_id
713
+ AND update_row.state = 'pending';
714
+ GET DIAGNOSTICS update_count = ROW_COUNT;
715
+
716
+ UPDATE session_goals goal_row
717
+ SET status = 'paused', paused_reason = 'api',
718
+ rationale = 'Session authority changed; explicit resume is required.',
719
+ version = goal_row.version + 1, updated_at = pg_catalog.clock_timestamp()
720
+ WHERE goal_row.workspace_id = p_workspace_id
721
+ AND goal_row.session_id = p_session_id
722
+ AND goal_row.status = 'active';
723
+ GET DIAGNOSTICS goal_count = ROW_COUNT;
724
+
725
+ UPDATE organization_user_resource_grants grant_row
726
+ SET status = 'revoked', revoked_at = pg_catalog.clock_timestamp(),
727
+ generation = grant_row.generation + 1, updated_at = pg_catalog.clock_timestamp()
728
+ WHERE grant_row.account_id = p_account_id
729
+ AND grant_row.workspace_id = p_workspace_id
730
+ AND grant_row.session_id = p_session_id
731
+ AND grant_row.authority_epoch = session_row.authority_epoch
732
+ AND grant_row.status = 'active';
733
+ GET DIAGNOSTICS grant_count = ROW_COUNT;
734
+
735
+ event_sequence := session_row.last_sequence + 1;
736
+ INSERT INTO session_visibility_write_capabilities (
737
+ backend_pid, transaction_id, capability_id
738
+ ) VALUES (
739
+ pg_catalog.pg_backend_pid(),
740
+ pg_catalog.pg_current_xact_id(),
741
+ visibility_write_capability_id
742
+ );
743
+ PERFORM pg_catalog.set_config(
744
+ 'opengeni.session_visibility_write_capability',
745
+ visibility_write_capability_id::text,
746
+ true
747
+ );
748
+ UPDATE sessions transition_target
749
+ SET visibility = p_target_visibility,
750
+ owner_organization_membership_id = new_owner_id,
751
+ owner_subject_id = session_row.owner_subject_id,
752
+ authority_epoch = new_epoch,
753
+ initial_personal_connection_delegations = '[]'::jsonb,
754
+ last_sequence = event_sequence,
755
+ updated_at = pg_catalog.clock_timestamp()
756
+ WHERE transition_target.id = p_session_id
757
+ AND transition_target.authority_epoch = session_row.authority_epoch;
758
+ IF NOT FOUND THEN
759
+ RAISE EXCEPTION 'session visibility transition lost authority epoch CAS'
760
+ USING ERRCODE = '40001';
761
+ END IF;
762
+ DELETE FROM session_visibility_write_capabilities capability
763
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
764
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id()
765
+ AND capability.capability_id = visibility_write_capability_id;
766
+ PERFORM pg_catalog.set_config(
767
+ 'opengeni.session_visibility_write_capability',
768
+ CASE WHEN previous_visibility_write_capability IS NULL
769
+ THEN '' ELSE previous_visibility_write_capability END,
770
+ true
771
+ );
772
+
773
+ INSERT INTO session_events (
774
+ account_id, workspace_id, session_id, sequence, type, payload, occurred_at
775
+ ) VALUES (
776
+ p_account_id, p_workspace_id, p_session_id, event_sequence,
777
+ 'session.visibility.changed',
778
+ pg_catalog.jsonb_build_object(
779
+ 'operationId', receipt_row.id,
780
+ 'fromVisibility', CASE session_row.visibility
781
+ WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
782
+ 'toVisibility', CASE p_target_visibility
783
+ WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
784
+ 'previousAuthorityEpoch', session_row.authority_epoch,
785
+ 'authorityEpoch', new_epoch,
786
+ 'interruptedAttemptCount', interruption_count,
787
+ 'cancelledTurnCount', turn_count,
788
+ 'cancelledUpdateCount', update_count,
789
+ 'pausedGoalCount', goal_count,
790
+ 'revokedGrantCount', grant_count
791
+ ),
792
+ pg_catalog.clock_timestamp()
793
+ );
794
+ END IF;
795
+
796
+ result_payload := pg_catalog.jsonb_build_object(
797
+ 'status', 'applied',
798
+ 'visibility', p_target_visibility,
799
+ 'authorityEpoch', new_epoch,
800
+ 'changed', session_row.visibility <> p_target_visibility,
801
+ 'interruptedAttemptCount', interruption_count,
802
+ 'cancelledTurnCount', turn_count,
803
+ 'cancelledUpdateCount', update_count,
804
+ 'pausedGoalCount', goal_count,
805
+ 'revokedGrantCount', grant_count
806
+ );
807
+ UPDATE session_command_receipts
808
+ SET result = result_payload, updated_at = pg_catalog.clock_timestamp()
809
+ WHERE id = receipt_row.id;
810
+
811
+ operation_id := receipt_row.id;
812
+ visibility := p_target_visibility;
813
+ authority_epoch := new_epoch;
814
+ owner_organization_membership_id := new_owner_id;
815
+ changed := session_row.visibility <> p_target_visibility;
816
+ replay := false;
817
+ interrupted_attempt_count := interruption_count;
818
+ cancelled_turn_count := turn_count;
819
+ cancelled_update_count := update_count;
820
+ paused_goal_count := goal_count;
821
+ revoked_grant_count := grant_count;
822
+ PERFORM pg_catalog.set_config(
823
+ 'opengeni.organization_tenancy_lifecycle',
824
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
825
+ true
826
+ );
827
+ RETURN NEXT;
828
+ EXCEPTION WHEN OTHERS THEN
829
+ PERFORM pg_catalog.set_config(
830
+ 'opengeni.organization_tenancy_lifecycle',
831
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
832
+ true
833
+ );
834
+ PERFORM pg_catalog.set_config(
835
+ 'opengeni.session_visibility_write_capability',
836
+ CASE WHEN previous_visibility_write_capability IS NULL
837
+ THEN '' ELSE previous_visibility_write_capability END,
838
+ true
839
+ );
840
+ RAISE;
841
+ END;
842
+ $body$;
843
+ $ddl$, data_schema);
844
+
845
+ EXECUTE format(
846
+ 'REVOKE ALL ON FUNCTION %I.transition_session_visibility(uuid,uuid,uuid,text,text,integer,text,text) FROM PUBLIC',
847
+ data_schema
848
+ );
849
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
850
+ EXECUTE format(
851
+ 'GRANT EXECUTE ON FUNCTION %I.transition_session_visibility(uuid,uuid,uuid,text,text,integer,text,text) TO opengeni_app',
852
+ data_schema
853
+ );
854
+ END IF;
855
+ END
856
+ $session_visibility_activation$;
857
+
858
+ COMMENT ON FUNCTION transition_session_visibility(uuid, uuid, uuid, text, text, integer, text, text) IS
859
+ 'Atomic owner-authorized visibility transition with authority-epoch CAS, stale-work fencing, grant revocation, and idempotent secret-safe receipt/event.';
860
+
861
+ DO $session_fork_activation$
862
+ DECLARE
863
+ data_schema text := current_schema();
864
+ BEGIN
865
+ EXECUTE format($ddl$
866
+ CREATE OR REPLACE FUNCTION %1$I.fork_session_content(
867
+ p_account_id uuid,
868
+ p_source_workspace_id uuid,
869
+ p_source_session_id uuid,
870
+ p_actor_subject_id text,
871
+ p_destination_workspace_id uuid,
872
+ p_destination_visibility text,
873
+ p_operation_key text,
874
+ p_canonical_request_hash text
875
+ )
876
+ RETURNS TABLE (
877
+ operation_id uuid,
878
+ session_id uuid,
879
+ workspace_id uuid,
880
+ visibility text,
881
+ authority_epoch integer,
882
+ copied_history_item_count integer,
883
+ replay boolean
884
+ )
885
+ LANGUAGE plpgsql
886
+ SECURITY DEFINER
887
+ SET search_path = %1$I, pg_catalog
888
+ AS $body$
889
+ DECLARE
890
+ actor_membership organization_memberships%%ROWTYPE;
891
+ source_session sessions%%ROWTYPE;
892
+ destination_workspace workspaces%%ROWTYPE;
893
+ receipt_row session_command_receipts%%ROWTYPE;
894
+ destination_session_id uuid;
895
+ destination_owner_id uuid;
896
+ history_count integer := 0;
897
+ destination_activity_revision bigint;
898
+ destination_depth integer;
899
+ destination_depth_source text;
900
+ destination_resources jsonb := '[]'::jsonb;
901
+ previous_workspace_id text := pg_catalog.current_setting('opengeni.workspace_id', true);
902
+ previous_lifecycle_marker text := pg_catalog.current_setting(
903
+ 'opengeni.organization_tenancy_lifecycle', true
904
+ );
905
+ previous_gate_state text := pg_catalog.current_setting(
906
+ 'opengeni.session_activity_gate_state', true
907
+ );
908
+ previous_gate_workspace_id text := pg_catalog.current_setting(
909
+ 'opengeni.session_activity_gate_workspace_id', true
910
+ );
911
+ previous_visibility_marker text := pg_catalog.current_setting(
912
+ 'opengeni.session_visibility_write_capability', true
913
+ );
914
+ visibility_write_capability_id uuid := pg_catalog.gen_random_uuid();
915
+ BEGIN
916
+ IF p_account_id IS NULL OR p_source_workspace_id IS NULL
917
+ OR p_source_session_id IS NULL OR p_actor_subject_id IS NULL
918
+ OR p_destination_workspace_id IS NULL OR p_destination_visibility IS NULL
919
+ OR p_operation_key IS NULL OR p_canonical_request_hash IS NULL
920
+ THEN
921
+ RAISE EXCEPTION 'session fork requires complete authority'
922
+ USING ERRCODE = '42501';
923
+ END IF;
924
+ IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
925
+ OR p_source_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
926
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
927
+ THEN
928
+ RAISE EXCEPTION 'session fork authority is invalid'
929
+ USING ERRCODE = '42501';
930
+ END IF;
931
+ IF p_destination_visibility NOT IN ('user_private', 'workspace_shared')
932
+ OR p_actor_subject_id <> pg_catalog.btrim(p_actor_subject_id)
933
+ OR pg_catalog.length(p_actor_subject_id) NOT BETWEEN 1 AND 1024
934
+ OR p_operation_key <> pg_catalog.btrim(p_operation_key)
935
+ OR pg_catalog.length(p_operation_key) NOT BETWEEN 1 AND 1024
936
+ OR p_canonical_request_hash !~ '^[0-9a-f]{64}$'
937
+ THEN
938
+ RAISE EXCEPTION 'session fork request is invalid'
939
+ USING ERRCODE = '22023';
940
+ END IF;
941
+ IF nullif(previous_gate_state, '') IS NOT NULL
942
+ OR nullif(previous_gate_workspace_id, '') IS NOT NULL
943
+ THEN
944
+ RAISE EXCEPTION 'session fork requires ownership of the activity gate'
945
+ USING ERRCODE = '55000';
946
+ END IF;
947
+
948
+ PERFORM pg_catalog.pg_advisory_xact_lock(
949
+ pg_catalog.hashtext(
950
+ 'session-fork-workspace:' || ordered_workspaces.lock_workspace_id::text
951
+ )
952
+ )
953
+ FROM (
954
+ SELECT DISTINCT requested.lock_workspace_id
955
+ FROM (VALUES (p_source_workspace_id), (p_destination_workspace_id))
956
+ AS requested(lock_workspace_id)
957
+ ORDER BY requested.lock_workspace_id
958
+ ) AS ordered_workspaces;
959
+
960
+ PERFORM pg_catalog.set_config(
961
+ 'opengeni.workspace_id', p_source_workspace_id::text, true
962
+ );
963
+ PERFORM 1 FROM workspaces workspace_row
964
+ WHERE workspace_row.account_id = p_account_id
965
+ AND workspace_row.id = p_source_workspace_id
966
+ FOR UPDATE;
967
+ IF NOT FOUND THEN
968
+ RAISE EXCEPTION 'session fork source workspace is unavailable'
969
+ USING ERRCODE = '42501';
970
+ END IF;
971
+ PERFORM pg_catalog.set_config(
972
+ 'opengeni.workspace_id', p_destination_workspace_id::text, true
973
+ );
974
+ PERFORM 1 FROM workspaces workspace_row
975
+ WHERE workspace_row.account_id = p_account_id
976
+ AND workspace_row.id = p_destination_workspace_id
977
+ FOR UPDATE;
978
+ IF NOT FOUND THEN
979
+ RAISE EXCEPTION 'session fork destination workspace is unavailable'
980
+ USING ERRCODE = '42501';
981
+ END IF;
982
+
983
+ PERFORM pg_catalog.set_config(
984
+ 'opengeni.organization_tenancy_lifecycle',
985
+ 'session_visibility_activation',
986
+ true
987
+ );
988
+ SELECT membership.* INTO actor_membership
989
+ FROM organization_memberships membership
990
+ WHERE membership.account_id = p_account_id
991
+ AND membership.subject_id = p_actor_subject_id
992
+ AND membership.status = 'active'
993
+ FOR UPDATE;
994
+ IF NOT FOUND THEN
995
+ RAISE EXCEPTION 'session fork requires active organization membership'
996
+ USING ERRCODE = '42501';
997
+ END IF;
998
+
999
+ PERFORM pg_catalog.set_config(
1000
+ 'opengeni.workspace_id', p_source_workspace_id::text, true
1001
+ );
1002
+ IF NOT EXISTS (
1003
+ SELECT 1 FROM workspace_memberships source_access
1004
+ WHERE source_access.account_id = p_account_id
1005
+ AND source_access.workspace_id = p_source_workspace_id
1006
+ AND source_access.subject_id = p_actor_subject_id
1007
+ ) THEN
1008
+ RAISE EXCEPTION 'session fork source workspace access is unavailable'
1009
+ USING ERRCODE = '42501';
1010
+ END IF;
1011
+ SELECT session.* INTO source_session
1012
+ FROM sessions session
1013
+ WHERE session.account_id = p_account_id
1014
+ AND session.workspace_id = p_source_workspace_id
1015
+ AND session.id = p_source_session_id
1016
+ FOR UPDATE;
1017
+ IF NOT FOUND THEN
1018
+ RAISE EXCEPTION 'session fork source session is unavailable'
1019
+ USING ERRCODE = 'P0002';
1020
+ END IF;
1021
+ IF source_session.visibility = 'user_private'
1022
+ AND source_session.owner_organization_membership_id <> actor_membership.id
1023
+ THEN
1024
+ RAISE EXCEPTION 'session fork source session is private'
1025
+ USING ERRCODE = '42501';
1026
+ END IF;
1027
+
1028
+ SELECT coalesce(
1029
+ pg_catalog.jsonb_agg(
1030
+ CASE WHEN resource.value ->> 'kind' = 'repository'
1031
+ THEN resource.value
1032
+ - 'credentialBindingId'
1033
+ - 'connectionId'
1034
+ - 'installationId'
1035
+ - 'projectId'
1036
+ - 'githubInstallationId'
1037
+ ELSE resource.value
1038
+ END
1039
+ ORDER BY resource.ordinality
1040
+ ),
1041
+ '[]'::jsonb
1042
+ ) INTO destination_resources
1043
+ FROM pg_catalog.jsonb_array_elements(source_session.resources)
1044
+ WITH ORDINALITY AS resource(value, ordinality);
1045
+
1046
+ DROP TABLE IF EXISTS pg_temp.opengeni_session_fork_history_spool;
1047
+ CREATE TEMP TABLE opengeni_session_fork_history_spool (
1048
+ position numeric NOT NULL,
1049
+ item jsonb NOT NULL,
1050
+ item_codec_version integer,
1051
+ active boolean NOT NULL,
1052
+ created_at timestamptz NOT NULL
1053
+ ) ON COMMIT DROP;
1054
+ INSERT INTO opengeni_session_fork_history_spool (
1055
+ position, item, item_codec_version, active, created_at
1056
+ )
1057
+ SELECT source_item.position, source_item.item,
1058
+ source_item.item_codec_version, source_item.active, source_item.created_at
1059
+ FROM session_history_items source_item
1060
+ WHERE source_item.account_id = p_account_id
1061
+ AND source_item.workspace_id = p_source_workspace_id
1062
+ AND source_item.session_id = p_source_session_id
1063
+ ORDER BY source_item.position;
1064
+ GET DIAGNOSTICS history_count = ROW_COUNT;
1065
+
1066
+ INSERT INTO session_command_receipts (
1067
+ account_id, workspace_id, actor_type, actor_subject_id, action,
1068
+ target_session_id, operation_key, canonical_request_hash
1069
+ ) VALUES (
1070
+ p_account_id, p_source_workspace_id, 'human', p_actor_subject_id,
1071
+ 'session.fork', p_source_session_id, p_operation_key,
1072
+ p_canonical_request_hash
1073
+ ) ON CONFLICT DO NOTHING;
1074
+ SELECT receipt.* INTO receipt_row
1075
+ FROM session_command_receipts receipt
1076
+ WHERE receipt.workspace_id = p_source_workspace_id
1077
+ AND receipt.actor_type = 'human'
1078
+ AND receipt.actor_subject_id = p_actor_subject_id
1079
+ AND receipt.actor_attempt_id IS NULL
1080
+ AND receipt.action = 'session.fork'
1081
+ AND receipt.target_session_id = p_source_session_id
1082
+ AND receipt.target_turn_id IS NULL
1083
+ AND receipt.operation_key = p_operation_key
1084
+ FOR UPDATE;
1085
+ IF NOT FOUND THEN
1086
+ RAISE EXCEPTION 'session fork receipt is unavailable'
1087
+ USING ERRCODE = 'P0002';
1088
+ END IF;
1089
+ IF receipt_row.canonical_request_hash <> p_canonical_request_hash THEN
1090
+ RAISE EXCEPTION 'session fork idempotency conflict'
1091
+ USING ERRCODE = '23505';
1092
+ END IF;
1093
+ IF receipt_row.result ->> 'status' = 'applied' THEN
1094
+ operation_id := receipt_row.id;
1095
+ session_id := (receipt_row.result ->> 'sessionId')::uuid;
1096
+ workspace_id := (receipt_row.result ->> 'workspaceId')::uuid;
1097
+ visibility := receipt_row.result ->> 'visibility';
1098
+ authority_epoch := 1;
1099
+ copied_history_item_count :=
1100
+ (receipt_row.result ->> 'copiedHistoryItemCount')::integer;
1101
+ replay := true;
1102
+ PERFORM pg_catalog.set_config(
1103
+ 'opengeni.workspace_id',
1104
+ CASE WHEN previous_workspace_id IS NULL THEN '' ELSE previous_workspace_id END,
1105
+ true
1106
+ );
1107
+ PERFORM pg_catalog.set_config(
1108
+ 'opengeni.organization_tenancy_lifecycle',
1109
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
1110
+ true
1111
+ );
1112
+ RETURN NEXT;
1113
+ RETURN;
1114
+ END IF;
1115
+
1116
+ PERFORM pg_catalog.set_config(
1117
+ 'opengeni.workspace_id', p_destination_workspace_id::text, true
1118
+ );
1119
+ IF NOT EXISTS (
1120
+ SELECT 1 FROM workspace_memberships destination_access
1121
+ WHERE destination_access.account_id = p_account_id
1122
+ AND destination_access.workspace_id = p_destination_workspace_id
1123
+ AND destination_access.subject_id = p_actor_subject_id
1124
+ ) THEN
1125
+ RAISE EXCEPTION 'session fork destination workspace access is unavailable'
1126
+ USING ERRCODE = '42501';
1127
+ END IF;
1128
+ SELECT * INTO destination_workspace
1129
+ FROM workspaces workspace_row
1130
+ WHERE workspace_row.account_id = p_account_id
1131
+ AND workspace_row.id = p_destination_workspace_id;
1132
+
1133
+ SELECT
1134
+ coalesce(
1135
+ CASE WHEN (destination_workspace.settings ->> 'maxNestedAgentDepth') ~ '^\d+$'
1136
+ THEN (destination_workspace.settings ->> 'maxNestedAgentDepth')::integer END,
1137
+ configuration.max_nested_agent_depth
1138
+ ),
1139
+ CASE WHEN (destination_workspace.settings ->> 'maxNestedAgentDepth') ~ '^\d+$'
1140
+ THEN 'workspace' ELSE configuration.policy_source END
1141
+ INTO destination_depth, destination_depth_source
1142
+ FROM nested_agent_depth_configuration configuration
1143
+ WHERE configuration.singleton = true;
1144
+ IF destination_depth IS NULL OR destination_depth_source IS NULL THEN
1145
+ RAISE EXCEPTION 'session fork destination depth policy is unavailable'
1146
+ USING ERRCODE = '55000';
1147
+ END IF;
1148
+
1149
+ destination_session_id := pg_catalog.gen_random_uuid();
1150
+ destination_owner_id := CASE WHEN p_destination_visibility = 'user_private'
1151
+ THEN actor_membership.id ELSE actor_membership.id END;
1152
+ INSERT INTO session_visibility_write_capabilities (
1153
+ backend_pid, transaction_id, capability_id
1154
+ ) VALUES (
1155
+ pg_catalog.pg_backend_pid(),
1156
+ pg_catalog.pg_current_xact_id(),
1157
+ visibility_write_capability_id
1158
+ );
1159
+ PERFORM pg_catalog.set_config(
1160
+ 'opengeni.session_visibility_write_capability',
1161
+ visibility_write_capability_id::text,
1162
+ true
1163
+ );
1164
+ PERFORM pg_catalog.set_config('opengeni.session_activity_gate_state', 'open', true);
1165
+ PERFORM pg_catalog.set_config(
1166
+ 'opengeni.session_activity_gate_workspace_id',
1167
+ p_destination_workspace_id::text,
1168
+ true
1169
+ );
1170
+
1171
+ INSERT INTO sessions (
1172
+ id, account_id, workspace_id, status,
1173
+ initial_message, initial_message_codec_version,
1174
+ title, title_source, instructions, policy_role,
1175
+ resources, skills, tools, metadata,
1176
+ created_by_kind, created_by_subject_id, created_by_context,
1177
+ owner_organization_membership_id, owner_subject_id,
1178
+ visibility, authority_epoch,
1179
+ forked_from_session_id, forked_from_authority_epoch,
1180
+ forked_from_visibility, forked_at,
1181
+ forked_by_organization_membership_id,
1182
+ model, sandbox_backend, sandbox_os, sandbox_group_id,
1183
+ first_party_mcp_permissions, first_party_mcp_tools,
1184
+ initial_personal_connection_delegations, tool_policy,
1185
+ root_session_id, nested_agent_depth,
1186
+ max_nested_agent_depth_override, effective_max_nested_agent_depth,
1187
+ nested_agent_depth_policy_source, nested_agent_depth_policy_session_id,
1188
+ temporal_workflow_id, active_turn_id, variable_set_id,
1189
+ rig_id, rig_version_id, active_sandbox_id, active_epoch,
1190
+ working_dir, codex_pinned_credential_id, codex_last_credential_id,
1191
+ codex_pin_source, codex_compaction_mode,
1192
+ queue_version, queue_head_position, queue_tail_position,
1193
+ last_sequence
1194
+ ) VALUES (
1195
+ destination_session_id, p_account_id, p_destination_workspace_id, 'idle',
1196
+ source_session.initial_message, source_session.initial_message_codec_version,
1197
+ source_session.title, source_session.title_source,
1198
+ source_session.instructions, source_session.policy_role,
1199
+ destination_resources, '[]'::jsonb, '[]'::jsonb, '{}'::jsonb,
1200
+ 'subject', p_actor_subject_id, '{"fork":true}'::jsonb,
1201
+ destination_owner_id, actor_membership.subject_id,
1202
+ p_destination_visibility, 1,
1203
+ p_source_session_id, source_session.authority_epoch,
1204
+ source_session.visibility, pg_catalog.clock_timestamp(), actor_membership.id,
1205
+ source_session.model, source_session.sandbox_backend,
1206
+ source_session.sandbox_os, destination_session_id,
1207
+ NULL, '[]'::jsonb, '[]'::jsonb,
1208
+ '{"mode":"explicit","inheritedFromSessionId":null}'::jsonb,
1209
+ destination_session_id, 0, NULL, destination_depth,
1210
+ destination_depth_source, NULL,
1211
+ NULL, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL,
1212
+ source_session.codex_compaction_mode,
1213
+ 0, 0, 0, 1
1214
+ );
1215
+
1216
+ INSERT INTO session_history_items (
1217
+ account_id, workspace_id, session_id, turn_id,
1218
+ position, item, item_codec_version, active,
1219
+ provider_artifact_invalidated_at,
1220
+ provider_artifact_invalidation_reason,
1221
+ provider_artifact_invalidated_by_attempt_id,
1222
+ created_at
1223
+ )
1224
+ SELECT p_account_id, p_destination_workspace_id, destination_session_id, NULL,
1225
+ source_item.position, source_item.item, source_item.item_codec_version,
1226
+ source_item.active, NULL, NULL, NULL, source_item.created_at
1227
+ FROM opengeni_session_fork_history_spool source_item
1228
+ ORDER BY source_item.position;
1229
+
1230
+ INSERT INTO session_events (
1231
+ account_id, workspace_id, session_id, sequence, type, payload, occurred_at
1232
+ ) VALUES (
1233
+ p_account_id, p_destination_workspace_id, destination_session_id, 1,
1234
+ 'session.created',
1235
+ pg_catalog.jsonb_build_object(
1236
+ 'forked', true,
1237
+ 'sourceSessionId', p_source_session_id,
1238
+ 'sourceAuthorityEpoch', source_session.authority_epoch,
1239
+ 'sourceVisibility', CASE source_session.visibility
1240
+ WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
1241
+ 'visibility', CASE p_destination_visibility
1242
+ WHEN 'user_private' THEN 'private' ELSE 'workspace' END,
1243
+ 'copiedHistoryItemCount', history_count
1244
+ ),
1245
+ pg_catalog.clock_timestamp()
1246
+ );
1247
+
1248
+ PERFORM pg_catalog.set_config('opengeni.session_activity_gate_state', 'preparing', true);
1249
+ SET CONSTRAINTS ALL IMMEDIATE;
1250
+ SET CONSTRAINTS sessions_activity_insert_commit_guard,
1251
+ sessions_activity_update_commit_guard DEFERRED;
1252
+ PERFORM pg_catalog.set_config('opengeni.session_activity_gate_state', 'finalizing', true);
1253
+ UPDATE workspace_session_activity_revisions counter
1254
+ SET revision = counter.revision + 1
1255
+ WHERE counter.workspace_id = p_destination_workspace_id
1256
+ RETURNING counter.revision INTO destination_activity_revision;
1257
+ IF destination_activity_revision IS NULL THEN
1258
+ RAISE EXCEPTION 'session fork destination activity counter is unavailable'
1259
+ USING ERRCODE = '55000';
1260
+ END IF;
1261
+ UPDATE sessions destination_session
1262
+ SET activity_revision = destination_activity_revision,
1263
+ activity_revision_pending_xid = NULL
1264
+ WHERE destination_session.id = destination_session_id
1265
+ AND destination_session.activity_revision_pending_xid
1266
+ = pg_catalog.pg_current_xact_id()::text::bigint;
1267
+ IF NOT FOUND THEN
1268
+ RAISE EXCEPTION 'session fork destination activity was not finalized'
1269
+ USING ERRCODE = '55000';
1270
+ END IF;
1271
+ SET CONSTRAINTS sessions_activity_insert_commit_guard,
1272
+ sessions_activity_update_commit_guard IMMEDIATE;
1273
+ PERFORM pg_catalog.set_config('opengeni.session_activity_gate_state', '', true);
1274
+ PERFORM pg_catalog.set_config('opengeni.session_activity_gate_workspace_id', '', true);
1275
+ PERFORM pg_catalog.set_config(
1276
+ 'opengeni.session_visibility_write_capability',
1277
+ CASE WHEN previous_visibility_marker IS NULL THEN '' ELSE previous_visibility_marker END,
1278
+ true
1279
+ );
1280
+ DELETE FROM session_visibility_write_capabilities capability
1281
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
1282
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id()
1283
+ AND capability.capability_id = visibility_write_capability_id;
1284
+
1285
+ PERFORM pg_catalog.set_config(
1286
+ 'opengeni.workspace_id', p_source_workspace_id::text, true
1287
+ );
1288
+ UPDATE session_command_receipts
1289
+ SET result = pg_catalog.jsonb_build_object(
1290
+ 'status', 'applied',
1291
+ 'sessionId', destination_session_id,
1292
+ 'workspaceId', p_destination_workspace_id,
1293
+ 'visibility', p_destination_visibility,
1294
+ 'authorityEpoch', 1,
1295
+ 'copiedHistoryItemCount', history_count
1296
+ ),
1297
+ updated_at = pg_catalog.clock_timestamp()
1298
+ WHERE id = receipt_row.id;
1299
+
1300
+ operation_id := receipt_row.id;
1301
+ session_id := destination_session_id;
1302
+ workspace_id := p_destination_workspace_id;
1303
+ visibility := p_destination_visibility;
1304
+ authority_epoch := 1;
1305
+ copied_history_item_count := history_count;
1306
+ replay := false;
1307
+ PERFORM pg_catalog.set_config(
1308
+ 'opengeni.workspace_id',
1309
+ CASE WHEN previous_workspace_id IS NULL THEN '' ELSE previous_workspace_id END,
1310
+ true
1311
+ );
1312
+ PERFORM pg_catalog.set_config(
1313
+ 'opengeni.organization_tenancy_lifecycle',
1314
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
1315
+ true
1316
+ );
1317
+ RETURN NEXT;
1318
+ EXCEPTION WHEN OTHERS THEN
1319
+ PERFORM pg_catalog.set_config(
1320
+ 'opengeni.workspace_id',
1321
+ CASE WHEN previous_workspace_id IS NULL THEN '' ELSE previous_workspace_id END,
1322
+ true
1323
+ );
1324
+ PERFORM pg_catalog.set_config(
1325
+ 'opengeni.organization_tenancy_lifecycle',
1326
+ CASE WHEN previous_lifecycle_marker IS NULL THEN '' ELSE previous_lifecycle_marker END,
1327
+ true
1328
+ );
1329
+ PERFORM pg_catalog.set_config(
1330
+ 'opengeni.session_activity_gate_state',
1331
+ CASE WHEN previous_gate_state IS NULL THEN '' ELSE previous_gate_state END,
1332
+ true
1333
+ );
1334
+ PERFORM pg_catalog.set_config(
1335
+ 'opengeni.session_activity_gate_workspace_id',
1336
+ CASE WHEN previous_gate_workspace_id IS NULL THEN '' ELSE previous_gate_workspace_id END,
1337
+ true
1338
+ );
1339
+ PERFORM pg_catalog.set_config(
1340
+ 'opengeni.session_visibility_write_capability',
1341
+ CASE WHEN previous_visibility_marker IS NULL THEN '' ELSE previous_visibility_marker END,
1342
+ true
1343
+ );
1344
+ RAISE;
1345
+ END;
1346
+ $body$;
1347
+ $ddl$, data_schema);
1348
+
1349
+ EXECUTE format(
1350
+ 'REVOKE ALL ON FUNCTION %I.fork_session_content(uuid,uuid,uuid,text,uuid,text,text,text) FROM PUBLIC',
1351
+ data_schema
1352
+ );
1353
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
1354
+ EXECUTE format(
1355
+ 'GRANT EXECUTE ON FUNCTION %I.fork_session_content(uuid,uuid,uuid,text,uuid,text,text,text) TO opengeni_app',
1356
+ data_schema
1357
+ );
1358
+ END IF;
1359
+ END
1360
+ $session_fork_activation$;
1361
+
1362
+ COMMENT ON FUNCTION fork_session_content(uuid, uuid, uuid, text, uuid, text, text, text) IS
1363
+ 'Creates an independent idle session from an explicit durable-content allowlist; copies no live runtime, provider authority, credentials, grants, delegations, pins, variables, rigs, turns, attempts, queues, goals, or pending work.';