@opengeni/db 3.2.0-canary.0 → 3.4.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canonical-human-identities.js +3 -3
- package/dist/{chunk-SIBYBA2D.js → chunk-6DPJA36C.js} +30 -11
- package/dist/chunk-6DPJA36C.js.map +1 -0
- package/dist/{chunk-KRLPEMLH.js → chunk-C2AFP7SB.js} +184 -6
- package/dist/chunk-C2AFP7SB.js.map +1 -0
- package/dist/{chunk-AZHW7R5V.js → chunk-H7MXYG7R.js} +12 -8
- package/dist/chunk-H7MXYG7R.js.map +1 -0
- package/dist/{chunk-2PRS4J5Q.js → chunk-HY3F5Q4E.js} +3 -3
- package/dist/{chunk-NAB6FKFN.js → chunk-O6FTELKH.js} +3 -3
- package/dist/{chunk-UZNXG44K.js → chunk-PYMY3AJW.js} +324 -8
- package/dist/chunk-PYMY3AJW.js.map +1 -0
- package/dist/{chunk-6HMM2R5T.js → chunk-TRBOWUG3.js} +98 -4
- package/dist/chunk-TRBOWUG3.js.map +1 -0
- package/dist/{chunk-E6CLMKEE.js → chunk-V3TRUXFJ.js} +2 -2
- package/dist/{chunk-2EH4WRHL.js → chunk-ZWRIPCTR.js} +2 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/governed-learning-activation.d.ts +16 -0
- package/dist/index.d.ts +108 -7
- package/dist/index.js +3605 -1417
- package/dist/index.js.map +1 -1
- package/dist/insights-model-bundle.d.ts +30 -0
- package/dist/insights-usage-bundle.d.ts +32 -0
- package/dist/insights.d.ts +10 -2
- package/dist/lossless-json.d.ts +1 -1
- package/dist/managed-user-setup.d.ts +82 -0
- package/dist/memory-domain.d.ts +2 -2
- package/dist/migrate.d.ts +3 -1
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +4 -4
- package/dist/organization-membership-lifecycle.d.ts +60 -1
- package/dist/pr-review.d.ts +44 -3
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +12 -7
- package/dist/schema.d.ts +839 -101
- package/dist/schema.js +9 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-tenancy.d.ts +12 -1
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/drizzle/0348_named_signup_and_user_setup.sql +838 -0
- package/drizzle/0349_greenfield_session_tenancy_activation.sql +792 -0
- package/drizzle/0350_organization_shared_workspace_administration.sql +889 -0
- package/drizzle/0351_organization_user_setup_delivery.sql +978 -0
- package/drizzle/0352_session_variable_set_attachments.sql +1661 -0
- package/drizzle/0353_automatic_session_title_policy_fence.sql +476 -0
- package/drizzle/0354_automatic_session_title_quarantine_index.sql +9 -0
- package/drizzle/0355_automatic_session_title_quarantine.sql +92 -0
- package/drizzle/0356_set_based_insights_session_visibility.sql +237 -0
- package/drizzle/0357_rig_platform_base_only.sql +44 -0
- package/drizzle/0358_pr_review_managed_github_app.sql +108 -0
- package/drizzle/0359_insights_force_rls_read_capability.sql +936 -0
- package/drizzle/0360_organization_identity_confirmation_prompt.sql +103 -0
- package/drizzle/0361_remember_knowledge_memory_materialization.sql +249 -0
- package/package.json +6 -6
- package/src/database.ts +12 -8
- package/src/governed-learning-activation.ts +74 -2
- package/src/index.ts +879 -92
- package/src/insights-model-bundle.ts +681 -0
- package/src/insights-usage-bundle.ts +240 -0
- package/src/insights.ts +265 -152
- package/src/lossless-json.ts +2 -1
- package/src/managed-user-setup.ts +316 -0
- package/src/memory-domain.ts +3 -3
- package/src/migrate.ts +38 -14
- package/src/new-session-drafts.ts +22 -15
- package/src/organization-membership-lifecycle.ts +253 -1
- package/src/pr-review.ts +449 -6
- package/src/provision-roles.ts +92 -0
- package/src/runtime-posture.ts +247 -4
- package/src/schema.ts +210 -4
- package/src/session-tenancy.ts +120 -2
- package/dist/chunk-6HMM2R5T.js.map +0 -1
- package/dist/chunk-AZHW7R5V.js.map +0 -1
- package/dist/chunk-KRLPEMLH.js.map +0 -1
- package/dist/chunk-SIBYBA2D.js.map +0 -1
- package/dist/chunk-UZNXG44K.js.map +0 -1
- /package/dist/{chunk-2PRS4J5Q.js.map → chunk-HY3F5Q4E.js.map} +0 -0
- /package/dist/{chunk-NAB6FKFN.js.map → chunk-O6FTELKH.js.map} +0 -0
- /package/dist/{chunk-E6CLMKEE.js.map → chunk-V3TRUXFJ.js.map} +0 -0
- /package/dist/{chunk-2EH4WRHL.js.map → chunk-ZWRIPCTR.js.map} +0 -0
|
@@ -0,0 +1,1661 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Activate ordered, FK-backed session Variable Set attachments. Runtime
|
|
3
|
+
-- injection now consumes the complete ordered selection, so every API/control/
|
|
4
|
+
-- turn worker must be stopped before this cutover and no pre-0352 image may
|
|
5
|
+
-- restart afterwards.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '10min';
|
|
9
|
+
|
|
10
|
+
DO $session_variable_set_writer_drain_before_lock$
|
|
11
|
+
DECLARE
|
|
12
|
+
configured_roles_text text := nullif(
|
|
13
|
+
current_setting('opengeni.migration_application_roles', true), ''
|
|
14
|
+
);
|
|
15
|
+
configured_roles jsonb;
|
|
16
|
+
BEGIN
|
|
17
|
+
IF configured_roles_text IS NULL THEN
|
|
18
|
+
RAISE EXCEPTION
|
|
19
|
+
'0352 session Variable Set attachments require an explicit application database role list'
|
|
20
|
+
USING ERRCODE = '55000';
|
|
21
|
+
END IF;
|
|
22
|
+
BEGIN
|
|
23
|
+
configured_roles := configured_roles_text::jsonb;
|
|
24
|
+
EXCEPTION WHEN OTHERS THEN
|
|
25
|
+
RAISE EXCEPTION
|
|
26
|
+
'0352 session Variable Set attachments received a malformed application database role list'
|
|
27
|
+
USING ERRCODE = '55000';
|
|
28
|
+
END;
|
|
29
|
+
IF jsonb_typeof(configured_roles) <> 'array'
|
|
30
|
+
OR jsonb_array_length(configured_roles) NOT BETWEEN 1 AND 16
|
|
31
|
+
OR EXISTS (
|
|
32
|
+
SELECT 1 FROM jsonb_array_elements(configured_roles) AS roles(value)
|
|
33
|
+
WHERE jsonb_typeof(value) <> 'string'
|
|
34
|
+
OR btrim(value #>> '{}') = ''
|
|
35
|
+
OR octet_length(value #>> '{}') > 63
|
|
36
|
+
)
|
|
37
|
+
OR (
|
|
38
|
+
SELECT count(*) FROM jsonb_array_elements_text(configured_roles)
|
|
39
|
+
) <> (
|
|
40
|
+
SELECT count(DISTINCT value)
|
|
41
|
+
FROM jsonb_array_elements_text(configured_roles) AS roles(value)
|
|
42
|
+
)
|
|
43
|
+
THEN
|
|
44
|
+
RAISE EXCEPTION
|
|
45
|
+
'0352 session Variable Set attachments received an invalid application database role list'
|
|
46
|
+
USING ERRCODE = '55000';
|
|
47
|
+
END IF;
|
|
48
|
+
IF EXISTS (
|
|
49
|
+
SELECT 1 FROM pg_stat_activity activity
|
|
50
|
+
JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
|
|
51
|
+
ON roles.role_name = activity.usename
|
|
52
|
+
WHERE activity.datname = current_database()
|
|
53
|
+
AND activity.pid <> pg_backend_pid()
|
|
54
|
+
)
|
|
55
|
+
THEN
|
|
56
|
+
RAISE EXCEPTION
|
|
57
|
+
'0352 session Variable Set attachments require all configured OpenGeni application database sessions to be stopped'
|
|
58
|
+
USING ERRCODE = '55000';
|
|
59
|
+
END IF;
|
|
60
|
+
END
|
|
61
|
+
$session_variable_set_writer_drain_before_lock$;
|
|
62
|
+
|
|
63
|
+
LOCK TABLE sessions IN ACCESS EXCLUSIVE MODE;
|
|
64
|
+
LOCK TABLE workspace_variable_sets IN ACCESS EXCLUSIVE MODE;
|
|
65
|
+
LOCK TABLE sandbox_leases IN ACCESS EXCLUSIVE MODE;
|
|
66
|
+
LOCK TABLE turn_personal_resource_attachment_receipts IN ACCESS EXCLUSIVE MODE;
|
|
67
|
+
LOCK TABLE turn_personal_resource_snapshots IN ACCESS EXCLUSIVE MODE;
|
|
68
|
+
LOCK TABLE rig_versions IN ACCESS EXCLUSIVE MODE;
|
|
69
|
+
LOCK TABLE session_events IN ACCESS EXCLUSIVE MODE;
|
|
70
|
+
LOCK TABLE audit_events IN ACCESS EXCLUSIVE MODE;
|
|
71
|
+
LOCK TABLE workspace_session_activity_revisions IN ACCESS EXCLUSIVE MODE;
|
|
72
|
+
LOCK TABLE session_command_receipts IN ACCESS EXCLUSIVE MODE;
|
|
73
|
+
|
|
74
|
+
ALTER TABLE sessions
|
|
75
|
+
ADD COLUMN variable_set_ids jsonb NOT NULL DEFAULT '[]'::jsonb;
|
|
76
|
+
|
|
77
|
+
ALTER TABLE sessions NO FORCE ROW LEVEL SECURITY;
|
|
78
|
+
DO $session_variable_set_backfill_workspace_fences$
|
|
79
|
+
DECLARE
|
|
80
|
+
workspace_id_value uuid;
|
|
81
|
+
BEGIN
|
|
82
|
+
FOR workspace_id_value IN
|
|
83
|
+
SELECT DISTINCT session_value.workspace_id
|
|
84
|
+
FROM sessions session_value
|
|
85
|
+
ORDER BY session_value.workspace_id
|
|
86
|
+
LOOP
|
|
87
|
+
PERFORM acquire_session_tenancy_fence(workspace_id_value);
|
|
88
|
+
END LOOP;
|
|
89
|
+
END
|
|
90
|
+
$session_variable_set_backfill_workspace_fences$;
|
|
91
|
+
UPDATE sessions
|
|
92
|
+
SET variable_set_ids = CASE
|
|
93
|
+
WHEN variable_set_id IS NULL THEN '[]'::jsonb
|
|
94
|
+
ELSE jsonb_build_array(variable_set_id::text)
|
|
95
|
+
END;
|
|
96
|
+
ALTER TABLE sessions FORCE ROW LEVEL SECURITY;
|
|
97
|
+
|
|
98
|
+
-- A maintenance drain prevents a mixed fleet during the DDL, while this
|
|
99
|
+
-- restrictive policy prevents an old API/control/turn worker from rejoining
|
|
100
|
+
-- afterwards. Current standalone connections use the PgBouncer-supported
|
|
101
|
+
-- application_name startup receipt; injected/embedded handles stamp the
|
|
102
|
+
-- transaction-local GUC in setRlsContext. Pre-0352 binaries carry neither.
|
|
103
|
+
CREATE FUNCTION opengeni_private.session_variable_set_attachments_protocol_v1_active()
|
|
104
|
+
RETURNS boolean
|
|
105
|
+
LANGUAGE plpgsql
|
|
106
|
+
STABLE
|
|
107
|
+
SET search_path = pg_catalog
|
|
108
|
+
AS $function$
|
|
109
|
+
BEGIN
|
|
110
|
+
IF current_setting('opengeni.session_variable_set_attachments_v1', true) = '1'
|
|
111
|
+
OR current_setting('application_name', true) =
|
|
112
|
+
'opengeni-lossless-v1-session-variable-sets-v1'
|
|
113
|
+
THEN
|
|
114
|
+
RETURN true;
|
|
115
|
+
END IF;
|
|
116
|
+
RAISE EXCEPTION
|
|
117
|
+
'session Variable Set attachments require an OpenGeni 0352-or-newer runtime'
|
|
118
|
+
USING ERRCODE = '55000';
|
|
119
|
+
END
|
|
120
|
+
$function$;
|
|
121
|
+
|
|
122
|
+
REVOKE ALL ON FUNCTION
|
|
123
|
+
opengeni_private.session_variable_set_attachments_protocol_v1_active()
|
|
124
|
+
FROM PUBLIC;
|
|
125
|
+
|
|
126
|
+
DO $session_variable_set_protocol_grants$
|
|
127
|
+
DECLARE
|
|
128
|
+
configured_role text;
|
|
129
|
+
BEGIN
|
|
130
|
+
FOR configured_role IN
|
|
131
|
+
SELECT role_value.rolname
|
|
132
|
+
FROM jsonb_array_elements_text(
|
|
133
|
+
current_setting('opengeni.migration_application_roles')::jsonb
|
|
134
|
+
) configured(value)
|
|
135
|
+
JOIN pg_catalog.pg_roles role_value
|
|
136
|
+
ON role_value.rolname = configured.value
|
|
137
|
+
LOOP
|
|
138
|
+
EXECUTE format(
|
|
139
|
+
'GRANT EXECUTE ON FUNCTION opengeni_private.session_variable_set_attachments_protocol_v1_active() TO %I',
|
|
140
|
+
configured_role
|
|
141
|
+
);
|
|
142
|
+
END LOOP;
|
|
143
|
+
END
|
|
144
|
+
$session_variable_set_protocol_grants$;
|
|
145
|
+
|
|
146
|
+
SELECT set_config('opengeni.session_variable_set_attachments_v1', '1', true);
|
|
147
|
+
|
|
148
|
+
-- Migration 0176 admitted the then-current createDb application_name as a
|
|
149
|
+
-- lossless-content writer. The 0352 startup receipt rotates that name, so keep
|
|
150
|
+
-- both generations lossless-aware while the sessions protocol gate separately
|
|
151
|
+
-- rejects the old generation.
|
|
152
|
+
CREATE OR REPLACE FUNCTION opengeni_private.fence_legacy_lossless_content_update()
|
|
153
|
+
RETURNS trigger
|
|
154
|
+
LANGUAGE plpgsql
|
|
155
|
+
SET search_path = pg_catalog
|
|
156
|
+
AS $function$
|
|
157
|
+
BEGIN
|
|
158
|
+
IF current_setting('opengeni.lossless_content_writer', true) = '1'
|
|
159
|
+
OR current_setting('application_name', true) IN (
|
|
160
|
+
'opengeni-lossless-v1',
|
|
161
|
+
'opengeni-lossless-v1-session-variable-sets-v1'
|
|
162
|
+
)
|
|
163
|
+
THEN
|
|
164
|
+
RETURN NEW;
|
|
165
|
+
END IF;
|
|
166
|
+
|
|
167
|
+
CASE TG_TABLE_NAME
|
|
168
|
+
WHEN 'sessions' THEN
|
|
169
|
+
IF NEW.initial_message IS DISTINCT FROM OLD.initial_message
|
|
170
|
+
AND NEW.initial_message_codec_version IS NOT DISTINCT FROM OLD.initial_message_codec_version THEN
|
|
171
|
+
NEW.initial_message_codec_version := NULL;
|
|
172
|
+
END IF;
|
|
173
|
+
WHEN 'session_realtime_entries' THEN
|
|
174
|
+
IF NEW.text IS DISTINCT FROM OLD.text
|
|
175
|
+
AND NEW.text_codec_version IS NOT DISTINCT FROM OLD.text_codec_version THEN
|
|
176
|
+
NEW.text_codec_version := NULL;
|
|
177
|
+
END IF;
|
|
178
|
+
IF NEW.payload IS DISTINCT FROM OLD.payload
|
|
179
|
+
AND NEW.payload_codec_version IS NOT DISTINCT FROM OLD.payload_codec_version THEN
|
|
180
|
+
NEW.payload_codec_version := NULL;
|
|
181
|
+
END IF;
|
|
182
|
+
WHEN 'knowledge_memories' THEN
|
|
183
|
+
IF NEW.text IS DISTINCT FROM OLD.text
|
|
184
|
+
AND NEW.text_codec_version IS NOT DISTINCT FROM OLD.text_codec_version THEN
|
|
185
|
+
NEW.text_codec_version := NULL;
|
|
186
|
+
END IF;
|
|
187
|
+
WHEN 'session_turns' THEN
|
|
188
|
+
IF NEW.prompt IS DISTINCT FROM OLD.prompt
|
|
189
|
+
AND NEW.prompt_codec_version IS NOT DISTINCT FROM OLD.prompt_codec_version THEN
|
|
190
|
+
NEW.prompt_codec_version := NULL;
|
|
191
|
+
END IF;
|
|
192
|
+
WHEN 'session_system_updates' THEN
|
|
193
|
+
IF NEW.summary IS DISTINCT FROM OLD.summary
|
|
194
|
+
AND NEW.summary_codec_version IS NOT DISTINCT FROM OLD.summary_codec_version THEN
|
|
195
|
+
NEW.summary_codec_version := NULL;
|
|
196
|
+
END IF;
|
|
197
|
+
IF NEW.payload IS DISTINCT FROM OLD.payload
|
|
198
|
+
AND NEW.payload_codec_version IS NOT DISTINCT FROM OLD.payload_codec_version THEN
|
|
199
|
+
NEW.payload_codec_version := NULL;
|
|
200
|
+
END IF;
|
|
201
|
+
WHEN 'session_system_update_outbox' THEN
|
|
202
|
+
IF NEW.summary IS DISTINCT FROM OLD.summary
|
|
203
|
+
AND NEW.summary_codec_version IS NOT DISTINCT FROM OLD.summary_codec_version THEN
|
|
204
|
+
NEW.summary_codec_version := NULL;
|
|
205
|
+
END IF;
|
|
206
|
+
IF NEW.payload IS DISTINCT FROM OLD.payload
|
|
207
|
+
AND NEW.payload_codec_version IS NOT DISTINCT FROM OLD.payload_codec_version THEN
|
|
208
|
+
NEW.payload_codec_version := NULL;
|
|
209
|
+
END IF;
|
|
210
|
+
WHEN 'session_events' THEN
|
|
211
|
+
IF NEW.payload IS DISTINCT FROM OLD.payload
|
|
212
|
+
AND NEW.payload_codec_version IS NOT DISTINCT FROM OLD.payload_codec_version THEN
|
|
213
|
+
NEW.payload_codec_version := NULL;
|
|
214
|
+
END IF;
|
|
215
|
+
WHEN 'host_export_outbox' THEN
|
|
216
|
+
IF NEW.payload IS DISTINCT FROM OLD.payload
|
|
217
|
+
AND NEW.payload_codec_version IS NOT DISTINCT FROM OLD.payload_codec_version THEN
|
|
218
|
+
NEW.payload_codec_version := NULL;
|
|
219
|
+
END IF;
|
|
220
|
+
WHEN 'host_export_consumers' THEN
|
|
221
|
+
IF NEW.last_error IS DISTINCT FROM OLD.last_error
|
|
222
|
+
AND NEW.last_error_codec_version IS NOT DISTINCT FROM OLD.last_error_codec_version THEN
|
|
223
|
+
NEW.last_error_codec_version := NULL;
|
|
224
|
+
END IF;
|
|
225
|
+
WHEN 'agent_run_states' THEN
|
|
226
|
+
IF NEW.serialized_run_state IS DISTINCT FROM OLD.serialized_run_state
|
|
227
|
+
AND NEW.serialized_run_state_codec_version IS NOT DISTINCT FROM OLD.serialized_run_state_codec_version THEN
|
|
228
|
+
NEW.serialized_run_state_codec_version := NULL;
|
|
229
|
+
END IF;
|
|
230
|
+
IF NEW.pending_approvals IS DISTINCT FROM OLD.pending_approvals
|
|
231
|
+
AND NEW.pending_approvals_codec_version IS NOT DISTINCT FROM OLD.pending_approvals_codec_version THEN
|
|
232
|
+
NEW.pending_approvals_codec_version := NULL;
|
|
233
|
+
END IF;
|
|
234
|
+
WHEN 'session_history_items' THEN
|
|
235
|
+
IF NEW.item IS DISTINCT FROM OLD.item
|
|
236
|
+
AND NEW.item_codec_version IS NOT DISTINCT FROM OLD.item_codec_version THEN
|
|
237
|
+
NEW.item_codec_version := NULL;
|
|
238
|
+
END IF;
|
|
239
|
+
WHEN 'session_pending_tool_calls' THEN
|
|
240
|
+
IF NEW.call_item IS DISTINCT FROM OLD.call_item
|
|
241
|
+
AND NEW.call_item_codec_version IS NOT DISTINCT FROM OLD.call_item_codec_version THEN
|
|
242
|
+
NEW.call_item_codec_version := NULL;
|
|
243
|
+
END IF;
|
|
244
|
+
IF NEW.result_item IS DISTINCT FROM OLD.result_item
|
|
245
|
+
AND NEW.result_item_codec_version IS NOT DISTINCT FROM OLD.result_item_codec_version THEN
|
|
246
|
+
NEW.result_item_codec_version := NULL;
|
|
247
|
+
END IF;
|
|
248
|
+
WHEN 'sandbox_session_envelopes' THEN
|
|
249
|
+
IF NEW.envelope IS DISTINCT FROM OLD.envelope
|
|
250
|
+
AND NEW.envelope_codec_version IS NOT DISTINCT FROM OLD.envelope_codec_version THEN
|
|
251
|
+
NEW.envelope_codec_version := NULL;
|
|
252
|
+
END IF;
|
|
253
|
+
WHEN 'session_recordings' THEN
|
|
254
|
+
IF NEW.reason IS DISTINCT FROM OLD.reason
|
|
255
|
+
AND NEW.reason_codec_version IS NOT DISTINCT FROM OLD.reason_codec_version THEN
|
|
256
|
+
NEW.reason_codec_version := NULL;
|
|
257
|
+
END IF;
|
|
258
|
+
WHEN 'host_export_dead_letters' THEN
|
|
259
|
+
IF NEW.envelope IS DISTINCT FROM OLD.envelope
|
|
260
|
+
AND NEW.envelope_codec_version IS NOT DISTINCT FROM OLD.envelope_codec_version THEN
|
|
261
|
+
NEW.envelope_codec_version := NULL;
|
|
262
|
+
END IF;
|
|
263
|
+
IF NEW.envelope #> '{event,payload}' IS DISTINCT FROM OLD.envelope #> '{event,payload}'
|
|
264
|
+
AND NEW.event_payload_codec_version IS NOT DISTINCT FROM OLD.event_payload_codec_version THEN
|
|
265
|
+
NEW.event_payload_codec_version := NULL;
|
|
266
|
+
END IF;
|
|
267
|
+
WHEN 'audit_events' THEN
|
|
268
|
+
IF NEW.metadata IS DISTINCT FROM OLD.metadata
|
|
269
|
+
AND NEW.metadata_codec_version IS NOT DISTINCT FROM OLD.metadata_codec_version THEN
|
|
270
|
+
NEW.metadata_codec_version := NULL;
|
|
271
|
+
END IF;
|
|
272
|
+
WHEN 'rig_changes' THEN
|
|
273
|
+
IF NEW.payload IS DISTINCT FROM OLD.payload
|
|
274
|
+
AND NEW.payload_codec_version IS NOT DISTINCT FROM OLD.payload_codec_version THEN
|
|
275
|
+
NEW.payload_codec_version := NULL;
|
|
276
|
+
END IF;
|
|
277
|
+
IF NEW.verification IS DISTINCT FROM OLD.verification
|
|
278
|
+
AND NEW.verification_codec_version IS NOT DISTINCT FROM OLD.verification_codec_version THEN
|
|
279
|
+
NEW.verification_codec_version := NULL;
|
|
280
|
+
END IF;
|
|
281
|
+
WHEN 'transcription_recordings' THEN
|
|
282
|
+
IF NEW.transcript_text IS DISTINCT FROM OLD.transcript_text
|
|
283
|
+
AND NEW.transcript_text_codec_version IS NOT DISTINCT FROM OLD.transcript_text_codec_version THEN
|
|
284
|
+
NEW.transcript_text_codec_version := NULL;
|
|
285
|
+
END IF;
|
|
286
|
+
WHEN 'transcription_recording_segments' THEN
|
|
287
|
+
IF NEW.transcript_text IS DISTINCT FROM OLD.transcript_text
|
|
288
|
+
AND NEW.transcript_text_codec_version IS NOT DISTINCT FROM OLD.transcript_text_codec_version THEN
|
|
289
|
+
NEW.transcript_text_codec_version := NULL;
|
|
290
|
+
END IF;
|
|
291
|
+
ELSE
|
|
292
|
+
RAISE EXCEPTION 'unsupported lossless-content fence table: %', TG_TABLE_NAME;
|
|
293
|
+
END CASE;
|
|
294
|
+
RETURN NEW;
|
|
295
|
+
END
|
|
296
|
+
$function$;
|
|
297
|
+
|
|
298
|
+
CREATE POLICY sessions_variable_set_attachments_protocol_v1
|
|
299
|
+
ON sessions
|
|
300
|
+
AS RESTRICTIVE
|
|
301
|
+
FOR ALL
|
|
302
|
+
TO PUBLIC
|
|
303
|
+
USING (opengeni_private.session_variable_set_attachments_protocol_v1_active())
|
|
304
|
+
WITH CHECK (opengeni_private.session_variable_set_attachments_protocol_v1_active());
|
|
305
|
+
|
|
306
|
+
CREATE TABLE session_variable_set_attachments (
|
|
307
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
308
|
+
account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
|
|
309
|
+
workspace_id uuid NOT NULL,
|
|
310
|
+
session_id uuid NOT NULL,
|
|
311
|
+
variable_set_id uuid NOT NULL REFERENCES workspace_variable_sets(id) ON DELETE CASCADE,
|
|
312
|
+
position integer NOT NULL,
|
|
313
|
+
session_status text NOT NULL,
|
|
314
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
315
|
+
CONSTRAINT session_variable_set_attachments_workspace_account_fk
|
|
316
|
+
FOREIGN KEY (workspace_id, account_id)
|
|
317
|
+
REFERENCES workspaces(id, account_id) ON DELETE CASCADE,
|
|
318
|
+
CONSTRAINT session_variable_set_attachments_session_fk
|
|
319
|
+
FOREIGN KEY (workspace_id, session_id)
|
|
320
|
+
REFERENCES sessions(workspace_id, id) ON DELETE CASCADE,
|
|
321
|
+
CONSTRAINT session_variable_set_attachments_position_check
|
|
322
|
+
CHECK (position >= 0 AND position < 25),
|
|
323
|
+
CONSTRAINT session_variable_set_attachments_status_check
|
|
324
|
+
CHECK (session_status IN (
|
|
325
|
+
'queued', 'running', 'idle', 'requires_action', 'recovering',
|
|
326
|
+
'waiting_capacity', 'failed', 'cancelled'
|
|
327
|
+
)),
|
|
328
|
+
CONSTRAINT session_variable_set_attachments_session_position_uq
|
|
329
|
+
UNIQUE (workspace_id, session_id, position),
|
|
330
|
+
CONSTRAINT session_variable_set_attachments_session_set_uq
|
|
331
|
+
UNIQUE (workspace_id, session_id, variable_set_id)
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
CREATE INDEX session_variable_set_attachments_set_sessions_idx
|
|
335
|
+
ON session_variable_set_attachments (
|
|
336
|
+
variable_set_id, session_status, workspace_id, session_id
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
INSERT INTO session_variable_set_attachments (
|
|
340
|
+
account_id, workspace_id, session_id, variable_set_id, position, session_status
|
|
341
|
+
)
|
|
342
|
+
SELECT account_id, workspace_id, id, variable_set_id, 0, status
|
|
343
|
+
FROM sessions
|
|
344
|
+
WHERE variable_set_id IS NOT NULL;
|
|
345
|
+
|
|
346
|
+
CREATE OR REPLACE FUNCTION normalize_session_variable_set_selection()
|
|
347
|
+
RETURNS trigger
|
|
348
|
+
LANGUAGE plpgsql
|
|
349
|
+
AS $body$
|
|
350
|
+
DECLARE
|
|
351
|
+
selected_count integer;
|
|
352
|
+
distinct_count integer;
|
|
353
|
+
last_id uuid;
|
|
354
|
+
BEGIN
|
|
355
|
+
IF TG_OP = 'INSERT'
|
|
356
|
+
AND NEW.variable_set_ids = '[]'::jsonb
|
|
357
|
+
AND NEW.variable_set_id IS NOT NULL
|
|
358
|
+
THEN
|
|
359
|
+
NEW.variable_set_ids := jsonb_build_array(NEW.variable_set_id::text);
|
|
360
|
+
ELSIF TG_OP = 'UPDATE'
|
|
361
|
+
AND NEW.variable_set_ids IS NOT DISTINCT FROM OLD.variable_set_ids
|
|
362
|
+
AND NEW.variable_set_id IS DISTINCT FROM OLD.variable_set_id
|
|
363
|
+
THEN
|
|
364
|
+
NEW.variable_set_ids := CASE
|
|
365
|
+
WHEN NEW.variable_set_id IS NULL THEN '[]'::jsonb
|
|
366
|
+
ELSE jsonb_build_array(NEW.variable_set_id::text)
|
|
367
|
+
END;
|
|
368
|
+
END IF;
|
|
369
|
+
|
|
370
|
+
IF jsonb_typeof(NEW.variable_set_ids) <> 'array' THEN
|
|
371
|
+
RAISE EXCEPTION 'variable_set_ids must be a JSON array' USING ERRCODE = '23514';
|
|
372
|
+
END IF;
|
|
373
|
+
selected_count := jsonb_array_length(NEW.variable_set_ids);
|
|
374
|
+
IF selected_count > 25 THEN
|
|
375
|
+
RAISE EXCEPTION 'variable_set_ids exceeds 25 entries' USING ERRCODE = '23514';
|
|
376
|
+
END IF;
|
|
377
|
+
IF EXISTS (
|
|
378
|
+
SELECT 1
|
|
379
|
+
FROM jsonb_array_elements(NEW.variable_set_ids) item(value)
|
|
380
|
+
WHERE jsonb_typeof(item.value) <> 'string'
|
|
381
|
+
OR (item.value #>> '{}') !~*
|
|
382
|
+
'^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
383
|
+
) THEN
|
|
384
|
+
RAISE EXCEPTION 'variable_set_ids must contain UUID strings' USING ERRCODE = '23514';
|
|
385
|
+
END IF;
|
|
386
|
+
SELECT count(DISTINCT value) INTO distinct_count
|
|
387
|
+
FROM jsonb_array_elements_text(NEW.variable_set_ids) selected(value);
|
|
388
|
+
IF distinct_count <> selected_count THEN
|
|
389
|
+
RAISE EXCEPTION 'variable_set_ids must not contain duplicates' USING ERRCODE = '23514';
|
|
390
|
+
END IF;
|
|
391
|
+
SELECT value::uuid INTO last_id
|
|
392
|
+
FROM jsonb_array_elements_text(NEW.variable_set_ids) WITH ORDINALITY selected(value, position)
|
|
393
|
+
ORDER BY position DESC
|
|
394
|
+
LIMIT 1;
|
|
395
|
+
IF NEW.variable_set_id IS DISTINCT FROM last_id THEN
|
|
396
|
+
RAISE EXCEPTION 'variable_set_id must match the last variable_set_ids entry'
|
|
397
|
+
USING ERRCODE = '23514';
|
|
398
|
+
END IF;
|
|
399
|
+
RETURN NEW;
|
|
400
|
+
END
|
|
401
|
+
$body$;
|
|
402
|
+
|
|
403
|
+
CREATE OR REPLACE FUNCTION sync_session_variable_set_attachments()
|
|
404
|
+
RETURNS trigger
|
|
405
|
+
LANGUAGE plpgsql
|
|
406
|
+
SECURITY DEFINER
|
|
407
|
+
SET search_path = pg_catalog
|
|
408
|
+
AS $body$
|
|
409
|
+
BEGIN
|
|
410
|
+
IF pg_trigger_depth() > 1 THEN
|
|
411
|
+
RETURN NEW;
|
|
412
|
+
END IF;
|
|
413
|
+
EXECUTE pg_catalog.format(
|
|
414
|
+
'DELETE FROM %I.session_variable_set_attachments
|
|
415
|
+
WHERE workspace_id = $1 AND session_id = $2',
|
|
416
|
+
TG_TABLE_SCHEMA
|
|
417
|
+
) USING NEW.workspace_id, NEW.id;
|
|
418
|
+
EXECUTE pg_catalog.format(
|
|
419
|
+
'INSERT INTO %I.session_variable_set_attachments (
|
|
420
|
+
account_id, workspace_id, session_id, variable_set_id, position, session_status
|
|
421
|
+
)
|
|
422
|
+
SELECT $1, $2, $3, selected.value::uuid,
|
|
423
|
+
selected.position::integer - 1, $5
|
|
424
|
+
FROM pg_catalog.jsonb_array_elements_text($4)
|
|
425
|
+
WITH ORDINALITY selected(value, position)',
|
|
426
|
+
TG_TABLE_SCHEMA
|
|
427
|
+
) USING NEW.account_id, NEW.workspace_id, NEW.id, NEW.variable_set_ids, NEW.status;
|
|
428
|
+
RETURN NEW;
|
|
429
|
+
END
|
|
430
|
+
$body$;
|
|
431
|
+
|
|
432
|
+
CREATE OR REPLACE FUNCTION sync_session_variable_set_attachment_status()
|
|
433
|
+
RETURNS trigger
|
|
434
|
+
LANGUAGE plpgsql
|
|
435
|
+
SECURITY DEFINER
|
|
436
|
+
SET search_path = pg_catalog
|
|
437
|
+
AS $body$
|
|
438
|
+
BEGIN
|
|
439
|
+
EXECUTE pg_catalog.format(
|
|
440
|
+
'UPDATE %I.session_variable_set_attachments
|
|
441
|
+
SET session_status = $1
|
|
442
|
+
WHERE workspace_id = $2 AND session_id = $3
|
|
443
|
+
AND session_status IS DISTINCT FROM $1',
|
|
444
|
+
TG_TABLE_SCHEMA
|
|
445
|
+
) USING NEW.status, NEW.workspace_id, NEW.id;
|
|
446
|
+
RETURN NEW;
|
|
447
|
+
END
|
|
448
|
+
$body$;
|
|
449
|
+
|
|
450
|
+
CREATE OR REPLACE FUNCTION guard_variable_set_session_attachments()
|
|
451
|
+
RETURNS trigger
|
|
452
|
+
LANGUAGE plpgsql
|
|
453
|
+
SECURITY DEFINER
|
|
454
|
+
SET search_path = pg_catalog
|
|
455
|
+
AS $body$
|
|
456
|
+
DECLARE
|
|
457
|
+
attachment_count integer := 0;
|
|
458
|
+
previous_account text := current_setting('opengeni.account_id', true);
|
|
459
|
+
previous_workspace text := current_setting('opengeni.workspace_id', true);
|
|
460
|
+
previous_subject text := current_setting('opengeni.subject_id', true);
|
|
461
|
+
BEGIN
|
|
462
|
+
PERFORM set_config('opengeni.account_id', OLD.account_id::text, true);
|
|
463
|
+
PERFORM set_config('opengeni.workspace_id', '', true);
|
|
464
|
+
PERFORM set_config('opengeni.subject_id', '', true);
|
|
465
|
+
EXECUTE format(
|
|
466
|
+
'SELECT count(*)::integer
|
|
467
|
+
FROM %I.session_variable_set_attachments
|
|
468
|
+
WHERE variable_set_id = $1',
|
|
469
|
+
TG_TABLE_SCHEMA
|
|
470
|
+
) INTO attachment_count USING OLD.id;
|
|
471
|
+
IF attachment_count > 0 THEN
|
|
472
|
+
RAISE EXCEPTION 'variable set remains attached to % sessions', attachment_count
|
|
473
|
+
USING ERRCODE = '23503';
|
|
474
|
+
END IF;
|
|
475
|
+
PERFORM set_config('opengeni.account_id', coalesce(previous_account, ''), true);
|
|
476
|
+
PERFORM set_config('opengeni.workspace_id', coalesce(previous_workspace, ''), true);
|
|
477
|
+
PERFORM set_config('opengeni.subject_id', coalesce(previous_subject, ''), true);
|
|
478
|
+
RETURN OLD;
|
|
479
|
+
EXCEPTION WHEN OTHERS THEN
|
|
480
|
+
PERFORM set_config('opengeni.account_id', coalesce(previous_account, ''), true);
|
|
481
|
+
PERFORM set_config('opengeni.workspace_id', coalesce(previous_workspace, ''), true);
|
|
482
|
+
PERFORM set_config('opengeni.subject_id', coalesce(previous_subject, ''), true);
|
|
483
|
+
RAISE;
|
|
484
|
+
END
|
|
485
|
+
$body$;
|
|
486
|
+
|
|
487
|
+
CREATE TRIGGER sessions_variable_set_selection_normalize
|
|
488
|
+
BEFORE INSERT OR UPDATE OF variable_set_ids, variable_set_id ON sessions
|
|
489
|
+
FOR EACH ROW EXECUTE FUNCTION normalize_session_variable_set_selection();
|
|
490
|
+
|
|
491
|
+
CREATE TRIGGER sessions_variable_set_attachments_sync
|
|
492
|
+
AFTER INSERT OR UPDATE OF variable_set_ids, variable_set_id ON sessions
|
|
493
|
+
FOR EACH ROW EXECUTE FUNCTION sync_session_variable_set_attachments();
|
|
494
|
+
|
|
495
|
+
CREATE TRIGGER sessions_variable_set_attachment_status_sync
|
|
496
|
+
AFTER UPDATE OF status ON sessions
|
|
497
|
+
FOR EACH ROW EXECUTE FUNCTION sync_session_variable_set_attachment_status();
|
|
498
|
+
|
|
499
|
+
CREATE TRIGGER workspace_variable_sets_session_attachment_guard
|
|
500
|
+
BEFORE DELETE ON workspace_variable_sets
|
|
501
|
+
FOR EACH ROW EXECUTE FUNCTION guard_variable_set_session_attachments();
|
|
502
|
+
|
|
503
|
+
REVOKE ALL ON FUNCTION normalize_session_variable_set_selection() FROM PUBLIC;
|
|
504
|
+
REVOKE ALL ON FUNCTION sync_session_variable_set_attachments() FROM PUBLIC;
|
|
505
|
+
REVOKE ALL ON FUNCTION sync_session_variable_set_attachment_status() FROM PUBLIC;
|
|
506
|
+
REVOKE ALL ON FUNCTION guard_variable_set_session_attachments() FROM PUBLIC;
|
|
507
|
+
|
|
508
|
+
ALTER TABLE session_variable_set_attachments ENABLE ROW LEVEL SECURITY;
|
|
509
|
+
ALTER TABLE session_variable_set_attachments FORCE ROW LEVEL SECURITY;
|
|
510
|
+
CREATE POLICY workspace_isolation ON session_variable_set_attachments
|
|
511
|
+
USING (
|
|
512
|
+
opengeni_private.workspace_rls_visible(account_id, workspace_id)
|
|
513
|
+
OR opengeni_private.variable_set_authority_capability_active('write')
|
|
514
|
+
)
|
|
515
|
+
WITH CHECK (
|
|
516
|
+
opengeni_private.workspace_rls_visible(account_id, workspace_id)
|
|
517
|
+
OR opengeni_private.variable_set_authority_capability_active('write')
|
|
518
|
+
);
|
|
519
|
+
CREATE POLICY session_visibility_isolation
|
|
520
|
+
ON session_variable_set_attachments AS RESTRICTIVE
|
|
521
|
+
USING (
|
|
522
|
+
session_reference_visible(account_id, workspace_id, session_id)
|
|
523
|
+
OR opengeni_private.variable_set_authority_capability_active('write')
|
|
524
|
+
)
|
|
525
|
+
WITH CHECK (
|
|
526
|
+
session_reference_visible(account_id, workspace_id, session_id)
|
|
527
|
+
OR opengeni_private.variable_set_authority_capability_active('write')
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
-- Retention finalization is the one lifecycle that must delete a departed
|
|
531
|
+
-- member's personal Variable Sets even when their already-revoked sessions
|
|
532
|
+
-- still retain an ordered selection. Ordinary deletion remains guarded above.
|
|
533
|
+
-- This private helper is callable only from the claimed retention finalizer;
|
|
534
|
+
-- it admits target-owned private or unowned workspace-shared sessions, refuses
|
|
535
|
+
-- another member's private/non-quiescent sessions, preserves survivor order,
|
|
536
|
+
-- rotates any warm quiescent sandbox group, and commits the selection,
|
|
537
|
+
-- projection, event, audit, and activity-clock writes as one transaction.
|
|
538
|
+
CREATE FUNCTION opengeni_private.detach_retention_variable_set_session_selections(
|
|
539
|
+
p_account_id uuid,
|
|
540
|
+
p_membership_id uuid,
|
|
541
|
+
p_operation_id uuid
|
|
542
|
+
) RETURNS integer
|
|
543
|
+
LANGUAGE plpgsql
|
|
544
|
+
SECURITY DEFINER
|
|
545
|
+
SET search_path FROM CURRENT
|
|
546
|
+
AS $body$
|
|
547
|
+
DECLARE
|
|
548
|
+
workspace_id_value uuid;
|
|
549
|
+
session_row sessions%ROWTYPE;
|
|
550
|
+
group_member_id uuid;
|
|
551
|
+
previous_ids jsonb;
|
|
552
|
+
next_ids jsonb;
|
|
553
|
+
removed_ids jsonb;
|
|
554
|
+
next_last_id uuid;
|
|
555
|
+
affected_session_ids uuid[];
|
|
556
|
+
activity_revision_value bigint;
|
|
557
|
+
detached_sessions integer := 0;
|
|
558
|
+
previous_workspace text := current_setting('opengeni.workspace_id', true);
|
|
559
|
+
previous_gate_state text := current_setting('opengeni.session_activity_gate_state', true);
|
|
560
|
+
previous_gate_workspace text := current_setting(
|
|
561
|
+
'opengeni.session_activity_gate_workspace_id', true
|
|
562
|
+
);
|
|
563
|
+
finalized_session_count integer;
|
|
564
|
+
write_capability_preexisting boolean;
|
|
565
|
+
fenced_access_capability_id uuid;
|
|
566
|
+
BEGIN
|
|
567
|
+
IF p_account_id IS NULL OR p_membership_id IS NULL OR p_operation_id IS NULL
|
|
568
|
+
OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
569
|
+
OR current_setting('opengeni.organization_tenancy_lifecycle', true)
|
|
570
|
+
IS DISTINCT FROM 'organization_membership_lifecycle'
|
|
571
|
+
OR nullif(previous_gate_state, '') IS NOT NULL
|
|
572
|
+
OR nullif(previous_gate_workspace, '') IS NOT NULL
|
|
573
|
+
THEN
|
|
574
|
+
RAISE EXCEPTION 'retention Variable Set detach authority is invalid'
|
|
575
|
+
USING ERRCODE = '42501';
|
|
576
|
+
END IF;
|
|
577
|
+
IF NOT EXISTS (
|
|
578
|
+
SELECT 1
|
|
579
|
+
FROM organization_memberships membership
|
|
580
|
+
JOIN organization_user_retention_deletions deletion
|
|
581
|
+
ON deletion.account_id = membership.account_id
|
|
582
|
+
AND deletion.membership_id = membership.id
|
|
583
|
+
WHERE membership.account_id = p_account_id
|
|
584
|
+
AND membership.id = p_membership_id
|
|
585
|
+
AND membership.status = 'revoked'
|
|
586
|
+
AND membership.personal_retention_until IS NOT NULL
|
|
587
|
+
AND membership.personal_retention_until <= clock_timestamp()
|
|
588
|
+
AND deletion.state = 'claimed'
|
|
589
|
+
AND deletion.claim_operation_id = p_operation_id
|
|
590
|
+
AND deletion.claim_expires_at > clock_timestamp()
|
|
591
|
+
AND deletion.database_finalized_at IS NULL
|
|
592
|
+
) THEN
|
|
593
|
+
RAISE EXCEPTION 'retention Variable Set detach claim is stale'
|
|
594
|
+
USING ERRCODE = '40001';
|
|
595
|
+
END IF;
|
|
596
|
+
SELECT opengeni_private.variable_set_authority_capability_active('write')
|
|
597
|
+
INTO write_capability_preexisting;
|
|
598
|
+
INSERT INTO opengeni_private.variable_set_authority_capabilities (
|
|
599
|
+
backend_pid, transaction_id, capability_kind
|
|
600
|
+
) VALUES (pg_backend_pid(), pg_current_xact_id(), 'write')
|
|
601
|
+
ON CONFLICT DO NOTHING;
|
|
602
|
+
fenced_access_capability_id :=
|
|
603
|
+
opengeni_private.open_session_tenancy_fenced_access(
|
|
604
|
+
session_tenancy_fence_target_schema()
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
FOR workspace_id_value IN
|
|
608
|
+
SELECT DISTINCT session_value.workspace_id
|
|
609
|
+
FROM sessions session_value
|
|
610
|
+
WHERE session_value.account_id = p_account_id
|
|
611
|
+
AND EXISTS (
|
|
612
|
+
SELECT 1
|
|
613
|
+
FROM session_variable_set_attachments attachment
|
|
614
|
+
JOIN workspace_variable_sets variable_set
|
|
615
|
+
ON variable_set.account_id = p_account_id
|
|
616
|
+
AND variable_set.id = attachment.variable_set_id
|
|
617
|
+
JOIN organization_user_resource_authorities authority
|
|
618
|
+
ON authority.account_id = variable_set.account_id
|
|
619
|
+
AND authority.id = variable_set.authority_id
|
|
620
|
+
AND authority.organization_membership_id = p_membership_id
|
|
621
|
+
AND authority.resource_kind = 'variable_set'
|
|
622
|
+
AND authority.resource_id = variable_set.id
|
|
623
|
+
WHERE attachment.account_id = p_account_id
|
|
624
|
+
AND attachment.workspace_id = session_value.workspace_id
|
|
625
|
+
AND attachment.session_id = session_value.id
|
|
626
|
+
AND variable_set.owner_organization_membership_id = p_membership_id
|
|
627
|
+
)
|
|
628
|
+
ORDER BY session_value.workspace_id
|
|
629
|
+
LOOP
|
|
630
|
+
PERFORM set_config('opengeni.workspace_id', workspace_id_value::text, true);
|
|
631
|
+
PERFORM 1 FROM workspace_inference_controls control
|
|
632
|
+
WHERE control.account_id = p_account_id
|
|
633
|
+
AND control.workspace_id = workspace_id_value
|
|
634
|
+
FOR SHARE;
|
|
635
|
+
PERFORM 1 FROM workspaces workspace
|
|
636
|
+
WHERE workspace.account_id = p_account_id
|
|
637
|
+
AND workspace.id = workspace_id_value
|
|
638
|
+
FOR KEY SHARE;
|
|
639
|
+
IF NOT FOUND THEN
|
|
640
|
+
RAISE EXCEPTION 'retention Variable Set detach workspace is unavailable'
|
|
641
|
+
USING ERRCODE = '40001';
|
|
642
|
+
END IF;
|
|
643
|
+
|
|
644
|
+
affected_session_ids := ARRAY[]::uuid[];
|
|
645
|
+
FOR session_row IN
|
|
646
|
+
SELECT session_value.*
|
|
647
|
+
FROM sessions session_value
|
|
648
|
+
WHERE session_value.account_id = p_account_id
|
|
649
|
+
AND session_value.workspace_id = workspace_id_value
|
|
650
|
+
AND EXISTS (
|
|
651
|
+
SELECT 1
|
|
652
|
+
FROM session_variable_set_attachments attachment
|
|
653
|
+
JOIN workspace_variable_sets variable_set
|
|
654
|
+
ON variable_set.account_id = p_account_id
|
|
655
|
+
AND variable_set.id = attachment.variable_set_id
|
|
656
|
+
JOIN organization_user_resource_authorities authority
|
|
657
|
+
ON authority.account_id = variable_set.account_id
|
|
658
|
+
AND authority.id = variable_set.authority_id
|
|
659
|
+
AND authority.organization_membership_id = p_membership_id
|
|
660
|
+
AND authority.resource_kind = 'variable_set'
|
|
661
|
+
AND authority.resource_id = variable_set.id
|
|
662
|
+
WHERE attachment.account_id = p_account_id
|
|
663
|
+
AND attachment.workspace_id = session_value.workspace_id
|
|
664
|
+
AND attachment.session_id = session_value.id
|
|
665
|
+
AND variable_set.owner_organization_membership_id = p_membership_id
|
|
666
|
+
)
|
|
667
|
+
ORDER BY session_value.id
|
|
668
|
+
FOR NO KEY UPDATE
|
|
669
|
+
LOOP
|
|
670
|
+
IF (
|
|
671
|
+
session_row.owner_organization_membership_id IS NOT NULL
|
|
672
|
+
AND session_row.owner_organization_membership_id IS DISTINCT FROM p_membership_id
|
|
673
|
+
)
|
|
674
|
+
OR session_row.active_turn_id IS NOT NULL
|
|
675
|
+
OR session_row.status NOT IN ('queued', 'idle', 'failed', 'cancelled')
|
|
676
|
+
THEN
|
|
677
|
+
RAISE EXCEPTION 'retention Variable Set detach requires a quiescent eligible session'
|
|
678
|
+
USING ERRCODE = '55000';
|
|
679
|
+
END IF;
|
|
680
|
+
PERFORM assert_session_tenancy_quiescent(
|
|
681
|
+
p_account_id, workspace_id_value, session_row.id, false
|
|
682
|
+
);
|
|
683
|
+
IF EXISTS (
|
|
684
|
+
SELECT 1 FROM sessions group_member
|
|
685
|
+
WHERE group_member.account_id = p_account_id
|
|
686
|
+
AND group_member.workspace_id = workspace_id_value
|
|
687
|
+
AND group_member.sandbox_group_id = session_row.sandbox_group_id
|
|
688
|
+
AND (
|
|
689
|
+
(
|
|
690
|
+
group_member.owner_organization_membership_id IS NOT NULL
|
|
691
|
+
AND group_member.owner_organization_membership_id
|
|
692
|
+
IS DISTINCT FROM p_membership_id
|
|
693
|
+
)
|
|
694
|
+
OR group_member.active_turn_id IS NOT NULL
|
|
695
|
+
OR group_member.status NOT IN ('queued', 'idle', 'failed', 'cancelled')
|
|
696
|
+
)
|
|
697
|
+
) THEN
|
|
698
|
+
RAISE EXCEPTION 'retention Variable Set detach cannot rotate a foreign or active sandbox group'
|
|
699
|
+
USING ERRCODE = '55000';
|
|
700
|
+
END IF;
|
|
701
|
+
FOR group_member_id IN
|
|
702
|
+
SELECT group_member.id
|
|
703
|
+
FROM sessions group_member
|
|
704
|
+
WHERE group_member.account_id = p_account_id
|
|
705
|
+
AND group_member.workspace_id = workspace_id_value
|
|
706
|
+
AND group_member.sandbox_group_id = session_row.sandbox_group_id
|
|
707
|
+
ORDER BY group_member.id
|
|
708
|
+
LOOP
|
|
709
|
+
PERFORM assert_session_tenancy_quiescent(
|
|
710
|
+
p_account_id, workspace_id_value, group_member_id, false
|
|
711
|
+
);
|
|
712
|
+
END LOOP;
|
|
713
|
+
|
|
714
|
+
PERFORM pg_advisory_xact_lock(hashtextextended(
|
|
715
|
+
'sandbox-lease-admission:' || workspace_id_value::text || ':'
|
|
716
|
+
|| session_row.sandbox_group_id::text,
|
|
717
|
+
0
|
|
718
|
+
));
|
|
719
|
+
PERFORM 1 FROM sandbox_leases lease
|
|
720
|
+
WHERE lease.account_id = p_account_id
|
|
721
|
+
AND lease.workspace_id = workspace_id_value
|
|
722
|
+
AND lease.sandbox_group_id = session_row.sandbox_group_id
|
|
723
|
+
FOR UPDATE;
|
|
724
|
+
IF EXISTS (
|
|
725
|
+
SELECT 1 FROM sandbox_lease_holders holder
|
|
726
|
+
JOIN sandbox_leases lease ON lease.id = holder.lease_id
|
|
727
|
+
WHERE lease.account_id = p_account_id
|
|
728
|
+
AND lease.workspace_id = workspace_id_value
|
|
729
|
+
AND lease.sandbox_group_id = session_row.sandbox_group_id
|
|
730
|
+
) THEN
|
|
731
|
+
RAISE EXCEPTION 'retention Variable Set detach requires an unheld sandbox group'
|
|
732
|
+
USING ERRCODE = '55000';
|
|
733
|
+
END IF;
|
|
734
|
+
|
|
735
|
+
previous_ids := coalesce(
|
|
736
|
+
session_row.variable_set_ids,
|
|
737
|
+
CASE WHEN session_row.variable_set_id IS NULL THEN '[]'::jsonb
|
|
738
|
+
ELSE jsonb_build_array(session_row.variable_set_id::text) END
|
|
739
|
+
);
|
|
740
|
+
SELECT coalesce(jsonb_agg(selected.value ORDER BY selected.position)
|
|
741
|
+
FILTER (WHERE variable_set.id IS NULL), '[]'::jsonb),
|
|
742
|
+
coalesce(jsonb_agg(selected.value ORDER BY selected.position)
|
|
743
|
+
FILTER (WHERE variable_set.id IS NOT NULL), '[]'::jsonb)
|
|
744
|
+
INTO next_ids, removed_ids
|
|
745
|
+
FROM jsonb_array_elements_text(previous_ids)
|
|
746
|
+
WITH ORDINALITY selected(value, position)
|
|
747
|
+
LEFT JOIN workspace_variable_sets variable_set
|
|
748
|
+
ON variable_set.account_id = p_account_id
|
|
749
|
+
AND variable_set.id = selected.value::uuid
|
|
750
|
+
AND variable_set.owner_organization_membership_id = p_membership_id
|
|
751
|
+
AND EXISTS (
|
|
752
|
+
SELECT 1 FROM organization_user_resource_authorities authority
|
|
753
|
+
WHERE authority.account_id = p_account_id
|
|
754
|
+
AND authority.id = variable_set.authority_id
|
|
755
|
+
AND authority.organization_membership_id = p_membership_id
|
|
756
|
+
AND authority.resource_kind = 'variable_set'
|
|
757
|
+
AND authority.resource_id = variable_set.id
|
|
758
|
+
);
|
|
759
|
+
IF removed_ids = '[]'::jsonb THEN
|
|
760
|
+
RAISE EXCEPTION 'retention Variable Set detach projection drifted from the session selection'
|
|
761
|
+
USING ERRCODE = '55000';
|
|
762
|
+
END IF;
|
|
763
|
+
SELECT selected.value::uuid INTO next_last_id
|
|
764
|
+
FROM jsonb_array_elements_text(next_ids)
|
|
765
|
+
WITH ORDINALITY selected(value, position)
|
|
766
|
+
ORDER BY selected.position DESC
|
|
767
|
+
LIMIT 1;
|
|
768
|
+
|
|
769
|
+
SET CONSTRAINTS sessions_activity_insert_commit_guard,
|
|
770
|
+
sessions_activity_update_commit_guard DEFERRED;
|
|
771
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'open', true);
|
|
772
|
+
PERFORM set_config(
|
|
773
|
+
'opengeni.session_activity_gate_workspace_id', workspace_id_value::text, true
|
|
774
|
+
);
|
|
775
|
+
UPDATE sessions affected SET
|
|
776
|
+
variable_set_ids = next_ids,
|
|
777
|
+
variable_set_id = next_last_id,
|
|
778
|
+
last_sequence = affected.last_sequence + 1,
|
|
779
|
+
updated_at = clock_timestamp()
|
|
780
|
+
WHERE affected.account_id = p_account_id
|
|
781
|
+
AND affected.workspace_id = workspace_id_value
|
|
782
|
+
AND affected.id = session_row.id;
|
|
783
|
+
INSERT INTO session_events (
|
|
784
|
+
account_id, workspace_id, session_id, sequence, type, payload,
|
|
785
|
+
payload_codec_version, occurred_at
|
|
786
|
+
) VALUES (
|
|
787
|
+
p_account_id, workspace_id_value, session_row.id,
|
|
788
|
+
session_row.last_sequence + 1, 'session.variable_sets.updated',
|
|
789
|
+
jsonb_build_object(
|
|
790
|
+
'actor', 'service:organization_retention',
|
|
791
|
+
'organizationMembershipId', p_membership_id,
|
|
792
|
+
'operationId', p_operation_id,
|
|
793
|
+
'variableSetIds', next_ids,
|
|
794
|
+
'removedIds', removed_ids,
|
|
795
|
+
'collisionPolicy', 'later_selected_set_wins',
|
|
796
|
+
'effectiveOn', 'next_turn_or_sandbox_operation',
|
|
797
|
+
'reason', 'owner_retention_deleted'
|
|
798
|
+
), 1, clock_timestamp()
|
|
799
|
+
);
|
|
800
|
+
INSERT INTO audit_events (
|
|
801
|
+
account_id, workspace_id, subject_id, action, target_type, target_id,
|
|
802
|
+
metadata, metadata_codec_version
|
|
803
|
+
)
|
|
804
|
+
SELECT p_account_id, workspace_id_value, NULL,
|
|
805
|
+
'session.variable_set.detached', 'session', session_row.id::text,
|
|
806
|
+
jsonb_build_object(
|
|
807
|
+
'actorKind', 'service',
|
|
808
|
+
'organizationMembershipId', p_membership_id,
|
|
809
|
+
'operationId', p_operation_id,
|
|
810
|
+
'variableSetId', removed.value,
|
|
811
|
+
'reason', 'owner_retention_deleted'
|
|
812
|
+
), 1
|
|
813
|
+
FROM jsonb_array_elements_text(removed_ids) removed(value);
|
|
814
|
+
UPDATE sandbox_leases lease SET
|
|
815
|
+
rotation_requested_at = coalesce(lease.rotation_requested_at, clock_timestamp()),
|
|
816
|
+
rotation_reason = coalesce(lease.rotation_reason, 'operator'),
|
|
817
|
+
expires_at = least(lease.expires_at, clock_timestamp()),
|
|
818
|
+
updated_at = clock_timestamp()
|
|
819
|
+
WHERE lease.account_id = p_account_id
|
|
820
|
+
AND lease.workspace_id = workspace_id_value
|
|
821
|
+
AND lease.sandbox_group_id = session_row.sandbox_group_id
|
|
822
|
+
AND lease.liveness <> 'cold';
|
|
823
|
+
|
|
824
|
+
affected_session_ids := array_append(affected_session_ids, session_row.id);
|
|
825
|
+
detached_sessions := detached_sessions + 1;
|
|
826
|
+
END LOOP;
|
|
827
|
+
|
|
828
|
+
IF coalesce(array_length(affected_session_ids, 1), 0) > 0 THEN
|
|
829
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'preparing', true);
|
|
830
|
+
SET CONSTRAINTS ALL IMMEDIATE;
|
|
831
|
+
SET CONSTRAINTS sessions_activity_insert_commit_guard,
|
|
832
|
+
sessions_activity_update_commit_guard DEFERRED;
|
|
833
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'finalizing', true);
|
|
834
|
+
UPDATE workspace_session_activity_revisions counter
|
|
835
|
+
SET revision = counter.revision + 1
|
|
836
|
+
WHERE counter.account_id = p_account_id
|
|
837
|
+
AND counter.workspace_id = workspace_id_value
|
|
838
|
+
RETURNING counter.revision INTO activity_revision_value;
|
|
839
|
+
IF activity_revision_value IS NULL THEN
|
|
840
|
+
RAISE EXCEPTION 'retention Variable Set detach activity counter is unavailable'
|
|
841
|
+
USING ERRCODE = '55000';
|
|
842
|
+
END IF;
|
|
843
|
+
UPDATE sessions affected SET
|
|
844
|
+
activity_revision = activity_revision_value,
|
|
845
|
+
activity_revision_pending_xid = NULL
|
|
846
|
+
WHERE affected.account_id = p_account_id
|
|
847
|
+
AND affected.workspace_id = workspace_id_value
|
|
848
|
+
AND affected.id = ANY(affected_session_ids)
|
|
849
|
+
AND affected.activity_revision_pending_xid = pg_current_xact_id()::text::bigint;
|
|
850
|
+
GET DIAGNOSTICS finalized_session_count = ROW_COUNT;
|
|
851
|
+
IF finalized_session_count <> array_length(affected_session_ids, 1) THEN
|
|
852
|
+
RAISE EXCEPTION 'retention Variable Set detach activity was not finalized'
|
|
853
|
+
USING ERRCODE = '55000';
|
|
854
|
+
END IF;
|
|
855
|
+
SET CONSTRAINTS sessions_activity_insert_commit_guard,
|
|
856
|
+
sessions_activity_update_commit_guard IMMEDIATE;
|
|
857
|
+
PERFORM set_config('opengeni.session_activity_gate_state', '', true);
|
|
858
|
+
PERFORM set_config('opengeni.session_activity_gate_workspace_id', '', true);
|
|
859
|
+
END IF;
|
|
860
|
+
END LOOP;
|
|
861
|
+
PERFORM opengeni_private.close_session_tenancy_fenced_access(
|
|
862
|
+
fenced_access_capability_id
|
|
863
|
+
);
|
|
864
|
+
IF NOT write_capability_preexisting THEN
|
|
865
|
+
DELETE FROM opengeni_private.variable_set_authority_capabilities
|
|
866
|
+
WHERE backend_pid = pg_backend_pid()
|
|
867
|
+
AND transaction_id = pg_current_xact_id_if_assigned()
|
|
868
|
+
AND capability_kind = 'write';
|
|
869
|
+
END IF;
|
|
870
|
+
PERFORM set_config('opengeni.workspace_id', coalesce(previous_workspace, ''), true);
|
|
871
|
+
RETURN detached_sessions;
|
|
872
|
+
EXCEPTION WHEN OTHERS THEN
|
|
873
|
+
PERFORM opengeni_private.close_session_tenancy_fenced_access(
|
|
874
|
+
fenced_access_capability_id
|
|
875
|
+
);
|
|
876
|
+
IF NOT coalesce(write_capability_preexisting, false) THEN
|
|
877
|
+
DELETE FROM opengeni_private.variable_set_authority_capabilities
|
|
878
|
+
WHERE backend_pid = pg_backend_pid()
|
|
879
|
+
AND transaction_id = pg_current_xact_id_if_assigned()
|
|
880
|
+
AND capability_kind = 'write';
|
|
881
|
+
END IF;
|
|
882
|
+
PERFORM set_config('opengeni.workspace_id', coalesce(previous_workspace, ''), true);
|
|
883
|
+
PERFORM set_config(
|
|
884
|
+
'opengeni.session_activity_gate_state', coalesce(previous_gate_state, ''), true
|
|
885
|
+
);
|
|
886
|
+
PERFORM set_config(
|
|
887
|
+
'opengeni.session_activity_gate_workspace_id',
|
|
888
|
+
coalesce(previous_gate_workspace, ''), true
|
|
889
|
+
);
|
|
890
|
+
RAISE;
|
|
891
|
+
END
|
|
892
|
+
$body$;
|
|
893
|
+
|
|
894
|
+
REVOKE ALL ON FUNCTION
|
|
895
|
+
opengeni_private.detach_retention_variable_set_session_selections(uuid,uuid,uuid)
|
|
896
|
+
FROM PUBLIC;
|
|
897
|
+
|
|
898
|
+
-- The source-definition anchor below contains the retained finalizer's
|
|
899
|
+
-- scheduled_tasks UPDATE and its authority lookup. Keep the migration owner
|
|
900
|
+
-- visible to those FORCE-RLS tables while the definition is inspected and
|
|
901
|
+
-- replaced; application roles remain policy-bound, and the maintenance drain
|
|
902
|
+
-- above excludes live writers.
|
|
903
|
+
ALTER TABLE scheduled_tasks NO FORCE ROW LEVEL SECURITY;
|
|
904
|
+
ALTER TABLE organization_user_resource_authorities NO FORCE ROW LEVEL SECURITY;
|
|
905
|
+
DO $patch_retention_variable_set_detach$
|
|
906
|
+
DECLARE
|
|
907
|
+
definition text;
|
|
908
|
+
patched text;
|
|
909
|
+
occurrences integer;
|
|
910
|
+
anchor constant text :=
|
|
911
|
+
E' UPDATE scheduled_tasks task SET status = ''paused'', variable_set_id = NULL,\n'
|
|
912
|
+
|| E' authority_revision = authority_revision + 1, updated_at = clock_timestamp()\n'
|
|
913
|
+
|| E' WHERE task.account_id = p_account_id AND task.variable_set_id IN (\n'
|
|
914
|
+
|| E' SELECT authority.resource_id FROM organization_user_resource_authorities authority\n'
|
|
915
|
+
|| E' WHERE authority.account_id = p_account_id\n'
|
|
916
|
+
|| E' AND authority.organization_membership_id = p_membership_id\n'
|
|
917
|
+
|| E' AND authority.resource_kind = ''variable_set''\n'
|
|
918
|
+
|| E' );';
|
|
919
|
+
replacement constant text :=
|
|
920
|
+
anchor
|
|
921
|
+
|| E'\n PERFORM opengeni_private.detach_retention_variable_set_session_selections(\n'
|
|
922
|
+
|| E' p_account_id, p_membership_id, p_operation_id\n'
|
|
923
|
+
|| E' );';
|
|
924
|
+
BEGIN
|
|
925
|
+
definition := pg_catalog.pg_get_functiondef(
|
|
926
|
+
pg_catalog.to_regprocedure(
|
|
927
|
+
pg_catalog.quote_ident(pg_catalog.current_schema())
|
|
928
|
+
|| '.finalize_organization_retention_deletion(uuid,uuid,uuid,text)'
|
|
929
|
+
)
|
|
930
|
+
);
|
|
931
|
+
occurrences := (
|
|
932
|
+
pg_catalog.length(definition)
|
|
933
|
+
- pg_catalog.length(pg_catalog.replace(definition, anchor, ''))
|
|
934
|
+
) / pg_catalog.length(anchor);
|
|
935
|
+
IF definition IS NULL OR occurrences <> 1
|
|
936
|
+
OR pg_catalog.strpos(
|
|
937
|
+
definition,
|
|
938
|
+
'detach_retention_variable_set_session_selections'
|
|
939
|
+
) > 0
|
|
940
|
+
THEN
|
|
941
|
+
RAISE EXCEPTION '0352 organization retention Variable Set detach definition drift'
|
|
942
|
+
USING ERRCODE = '55000';
|
|
943
|
+
END IF;
|
|
944
|
+
patched := pg_catalog.replace(definition, anchor, replacement);
|
|
945
|
+
IF pg_catalog.strpos(patched, replacement) = 0 THEN
|
|
946
|
+
RAISE EXCEPTION '0352 organization retention Variable Set detach patch failed'
|
|
947
|
+
USING ERRCODE = '55000';
|
|
948
|
+
END IF;
|
|
949
|
+
EXECUTE patched;
|
|
950
|
+
END
|
|
951
|
+
$patch_retention_variable_set_detach$;
|
|
952
|
+
ALTER TABLE organization_user_resource_authorities FORCE ROW LEVEL SECURITY;
|
|
953
|
+
ALTER TABLE scheduled_tasks FORCE ROW LEVEL SECURITY;
|
|
954
|
+
|
|
955
|
+
-- A restart/fork with runtime setup is one database transaction. The original
|
|
956
|
+
-- fork function still owns destination creation, history copy, receipt, and
|
|
957
|
+
-- source quiescence. This helper configures only that fresh singleton
|
|
958
|
+
-- destination before the outer function can commit, so no caller can observe
|
|
959
|
+
-- a fork that is missing its selected Rig or explicit Variable Sets.
|
|
960
|
+
CREATE FUNCTION opengeni_private.configure_fork_session_runtime(
|
|
961
|
+
p_account_id uuid,
|
|
962
|
+
p_workspace_id uuid,
|
|
963
|
+
p_source_session_id uuid,
|
|
964
|
+
p_destination_session_id uuid,
|
|
965
|
+
p_actor_subject_id text,
|
|
966
|
+
p_variable_set_ids jsonb,
|
|
967
|
+
p_rig_id uuid,
|
|
968
|
+
p_rig_version_id uuid,
|
|
969
|
+
p_configuration_digest text
|
|
970
|
+
) RETURNS TABLE (
|
|
971
|
+
runtime_event_id uuid,
|
|
972
|
+
runtime_event_sequence integer
|
|
973
|
+
)
|
|
974
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
975
|
+
SET search_path FROM CURRENT
|
|
976
|
+
AS $body$
|
|
977
|
+
DECLARE
|
|
978
|
+
destination_session sessions%ROWTYPE;
|
|
979
|
+
runtime_marker jsonb;
|
|
980
|
+
selected_count integer;
|
|
981
|
+
selected_last uuid;
|
|
982
|
+
destination_activity_revision bigint;
|
|
983
|
+
previous_gate_state text := current_setting('opengeni.session_activity_gate_state', true);
|
|
984
|
+
previous_gate_workspace text := current_setting(
|
|
985
|
+
'opengeni.session_activity_gate_workspace_id', true
|
|
986
|
+
);
|
|
987
|
+
BEGIN
|
|
988
|
+
IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
989
|
+
OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
|
|
990
|
+
OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
991
|
+
THEN
|
|
992
|
+
RAISE EXCEPTION 'fork runtime configuration authority is invalid'
|
|
993
|
+
USING ERRCODE = '42501';
|
|
994
|
+
END IF;
|
|
995
|
+
IF p_account_id IS NULL OR p_workspace_id IS NULL
|
|
996
|
+
OR p_source_session_id IS NULL OR p_destination_session_id IS NULL
|
|
997
|
+
OR p_actor_subject_id IS NULL OR p_variable_set_ids IS NULL
|
|
998
|
+
OR p_configuration_digest !~ '^[0-9a-f]{64}$'
|
|
999
|
+
OR nullif(previous_gate_state, '') IS NOT NULL
|
|
1000
|
+
OR nullif(previous_gate_workspace, '') IS NOT NULL
|
|
1001
|
+
THEN
|
|
1002
|
+
RAISE EXCEPTION 'fork runtime configuration request is invalid'
|
|
1003
|
+
USING ERRCODE = '22023';
|
|
1004
|
+
END IF;
|
|
1005
|
+
|
|
1006
|
+
PERFORM acquire_session_tenancy_fence(p_workspace_id);
|
|
1007
|
+
SELECT session_value.* INTO destination_session
|
|
1008
|
+
FROM sessions session_value
|
|
1009
|
+
WHERE session_value.account_id = p_account_id
|
|
1010
|
+
AND session_value.workspace_id = p_workspace_id
|
|
1011
|
+
AND session_value.id = p_destination_session_id
|
|
1012
|
+
FOR UPDATE;
|
|
1013
|
+
IF NOT FOUND
|
|
1014
|
+
OR destination_session.forked_from_session_id IS DISTINCT FROM p_source_session_id
|
|
1015
|
+
OR destination_session.owner_subject_id IS DISTINCT FROM p_actor_subject_id
|
|
1016
|
+
THEN
|
|
1017
|
+
RAISE EXCEPTION 'fork runtime destination is unavailable'
|
|
1018
|
+
USING ERRCODE = '42501';
|
|
1019
|
+
END IF;
|
|
1020
|
+
|
|
1021
|
+
runtime_marker := destination_session.metadata -> 'forkRuntimeConfiguration';
|
|
1022
|
+
IF runtime_marker IS NOT NULL THEN
|
|
1023
|
+
IF runtime_marker ->> 'digest' IS DISTINCT FROM p_configuration_digest THEN
|
|
1024
|
+
RAISE EXCEPTION 'session fork idempotency conflict' USING ERRCODE = '23505';
|
|
1025
|
+
END IF;
|
|
1026
|
+
runtime_event_id := nullif(runtime_marker ->> 'eventId', '')::uuid;
|
|
1027
|
+
runtime_event_sequence := (runtime_marker ->> 'eventSequence')::integer;
|
|
1028
|
+
IF runtime_event_id IS NULL OR runtime_event_sequence IS NULL THEN
|
|
1029
|
+
RAISE EXCEPTION 'fork runtime configuration marker is incomplete'
|
|
1030
|
+
USING ERRCODE = '23514';
|
|
1031
|
+
END IF;
|
|
1032
|
+
RETURN NEXT;
|
|
1033
|
+
RETURN;
|
|
1034
|
+
END IF;
|
|
1035
|
+
|
|
1036
|
+
IF destination_session.status <> 'idle'
|
|
1037
|
+
OR destination_session.active_turn_id IS NOT NULL
|
|
1038
|
+
OR destination_session.active_sandbox_id IS NOT NULL
|
|
1039
|
+
OR destination_session.sandbox_group_id IS DISTINCT FROM destination_session.id
|
|
1040
|
+
THEN
|
|
1041
|
+
RAISE EXCEPTION 'fork runtime destination is not fresh and quiescent'
|
|
1042
|
+
USING ERRCODE = '23514';
|
|
1043
|
+
END IF;
|
|
1044
|
+
IF jsonb_typeof(p_variable_set_ids) <> 'array' THEN
|
|
1045
|
+
RAISE EXCEPTION 'fork runtime Variable Set ids must be an array'
|
|
1046
|
+
USING ERRCODE = '22023';
|
|
1047
|
+
END IF;
|
|
1048
|
+
selected_count := jsonb_array_length(p_variable_set_ids);
|
|
1049
|
+
IF selected_count > 25
|
|
1050
|
+
OR EXISTS (
|
|
1051
|
+
SELECT 1 FROM jsonb_array_elements(p_variable_set_ids) item(value)
|
|
1052
|
+
WHERE jsonb_typeof(item.value) <> 'string'
|
|
1053
|
+
OR (item.value #>> '{}') !~*
|
|
1054
|
+
'^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
|
|
1055
|
+
)
|
|
1056
|
+
OR selected_count <> (
|
|
1057
|
+
SELECT count(DISTINCT value)
|
|
1058
|
+
FROM jsonb_array_elements_text(p_variable_set_ids) selected(value)
|
|
1059
|
+
)
|
|
1060
|
+
THEN
|
|
1061
|
+
RAISE EXCEPTION 'fork runtime Variable Set selection is invalid'
|
|
1062
|
+
USING ERRCODE = '22023';
|
|
1063
|
+
END IF;
|
|
1064
|
+
IF selected_count <> (
|
|
1065
|
+
SELECT count(*)
|
|
1066
|
+
FROM workspace_variable_sets variable_set
|
|
1067
|
+
JOIN jsonb_array_elements_text(p_variable_set_ids) selected(value)
|
|
1068
|
+
ON selected.value::uuid = variable_set.id
|
|
1069
|
+
WHERE variable_set.account_id = p_account_id
|
|
1070
|
+
AND variable_set.status = 'active'
|
|
1071
|
+
) THEN
|
|
1072
|
+
RAISE EXCEPTION 'fork runtime Variable Set selection is unavailable'
|
|
1073
|
+
USING ERRCODE = '42501';
|
|
1074
|
+
END IF;
|
|
1075
|
+
SELECT selected.value::uuid INTO selected_last
|
|
1076
|
+
FROM jsonb_array_elements_text(p_variable_set_ids)
|
|
1077
|
+
WITH ORDINALITY selected(value, position)
|
|
1078
|
+
ORDER BY selected.position DESC
|
|
1079
|
+
LIMIT 1;
|
|
1080
|
+
|
|
1081
|
+
IF (p_rig_id IS NULL) <> (p_rig_version_id IS NULL)
|
|
1082
|
+
OR (
|
|
1083
|
+
p_rig_id IS NOT NULL
|
|
1084
|
+
AND NOT EXISTS (
|
|
1085
|
+
SELECT 1
|
|
1086
|
+
FROM rig_versions rig_version
|
|
1087
|
+
WHERE rig_version.account_id = p_account_id
|
|
1088
|
+
AND rig_version.rig_id = p_rig_id
|
|
1089
|
+
AND rig_version.id = p_rig_version_id
|
|
1090
|
+
AND rig_version.active
|
|
1091
|
+
AND NOT EXISTS (
|
|
1092
|
+
SELECT 1
|
|
1093
|
+
FROM jsonb_array_elements_text(
|
|
1094
|
+
coalesce(rig_version.default_variable_set_ids, '[]'::jsonb)
|
|
1095
|
+
) default_set(value)
|
|
1096
|
+
WHERE NOT EXISTS (
|
|
1097
|
+
SELECT 1
|
|
1098
|
+
FROM workspace_variable_sets variable_set
|
|
1099
|
+
WHERE variable_set.account_id = p_account_id
|
|
1100
|
+
AND variable_set.id = default_set.value::uuid
|
|
1101
|
+
AND variable_set.status = 'active'
|
|
1102
|
+
)
|
|
1103
|
+
)
|
|
1104
|
+
)
|
|
1105
|
+
)
|
|
1106
|
+
THEN
|
|
1107
|
+
RAISE EXCEPTION 'fork runtime Rig selection is unavailable'
|
|
1108
|
+
USING ERRCODE = '42501';
|
|
1109
|
+
END IF;
|
|
1110
|
+
|
|
1111
|
+
runtime_event_id := gen_random_uuid();
|
|
1112
|
+
runtime_event_sequence := destination_session.last_sequence + 1;
|
|
1113
|
+
SET CONSTRAINTS sessions_activity_insert_commit_guard,
|
|
1114
|
+
sessions_activity_update_commit_guard DEFERRED;
|
|
1115
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'open', true);
|
|
1116
|
+
PERFORM set_config('opengeni.session_activity_gate_workspace_id', p_workspace_id::text, true);
|
|
1117
|
+
UPDATE sessions session_value SET
|
|
1118
|
+
variable_set_ids = p_variable_set_ids,
|
|
1119
|
+
variable_set_id = selected_last,
|
|
1120
|
+
rig_id = p_rig_id,
|
|
1121
|
+
rig_version_id = p_rig_version_id,
|
|
1122
|
+
metadata = session_value.metadata || jsonb_build_object(
|
|
1123
|
+
'forkRuntimeConfiguration', jsonb_build_object(
|
|
1124
|
+
'digest', p_configuration_digest,
|
|
1125
|
+
'eventId', runtime_event_id,
|
|
1126
|
+
'eventSequence', runtime_event_sequence,
|
|
1127
|
+
'variableSetIds', p_variable_set_ids,
|
|
1128
|
+
'rigId', p_rig_id,
|
|
1129
|
+
'rigVersionId', p_rig_version_id
|
|
1130
|
+
)
|
|
1131
|
+
),
|
|
1132
|
+
last_sequence = runtime_event_sequence,
|
|
1133
|
+
updated_at = clock_timestamp()
|
|
1134
|
+
WHERE session_value.account_id = p_account_id
|
|
1135
|
+
AND session_value.workspace_id = p_workspace_id
|
|
1136
|
+
AND session_value.id = p_destination_session_id;
|
|
1137
|
+
|
|
1138
|
+
INSERT INTO session_events (
|
|
1139
|
+
id, account_id, workspace_id, session_id, sequence, type, payload, occurred_at
|
|
1140
|
+
) VALUES (
|
|
1141
|
+
runtime_event_id, p_account_id, p_workspace_id, p_destination_session_id,
|
|
1142
|
+
runtime_event_sequence, 'session.runtime.configured', jsonb_build_object(
|
|
1143
|
+
'sourceSessionId', p_source_session_id,
|
|
1144
|
+
'variableSetIds', p_variable_set_ids,
|
|
1145
|
+
'rigId', p_rig_id,
|
|
1146
|
+
'rigVersionId', p_rig_version_id,
|
|
1147
|
+
'collisionPolicy', 'later_selected_set_wins'
|
|
1148
|
+
), clock_timestamp()
|
|
1149
|
+
);
|
|
1150
|
+
INSERT INTO audit_events (
|
|
1151
|
+
account_id, workspace_id, subject_id, action, target_type, target_id,
|
|
1152
|
+
metadata, metadata_codec_version
|
|
1153
|
+
) VALUES (
|
|
1154
|
+
p_account_id, p_workspace_id, p_actor_subject_id,
|
|
1155
|
+
'session.runtime.configured', 'session', p_destination_session_id::text,
|
|
1156
|
+
jsonb_build_object(
|
|
1157
|
+
'sourceSessionId', p_source_session_id,
|
|
1158
|
+
'variableSetIds', p_variable_set_ids,
|
|
1159
|
+
'rigId', p_rig_id,
|
|
1160
|
+
'rigVersionId', p_rig_version_id,
|
|
1161
|
+
'collisionPolicy', 'later_selected_set_wins'
|
|
1162
|
+
), 1
|
|
1163
|
+
);
|
|
1164
|
+
|
|
1165
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'preparing', true);
|
|
1166
|
+
SET CONSTRAINTS ALL IMMEDIATE;
|
|
1167
|
+
SET CONSTRAINTS sessions_activity_insert_commit_guard,
|
|
1168
|
+
sessions_activity_update_commit_guard DEFERRED;
|
|
1169
|
+
PERFORM set_config('opengeni.session_activity_gate_state', 'finalizing', true);
|
|
1170
|
+
UPDATE workspace_session_activity_revisions counter
|
|
1171
|
+
SET revision = counter.revision + 1
|
|
1172
|
+
WHERE counter.workspace_id = p_workspace_id
|
|
1173
|
+
RETURNING counter.revision INTO destination_activity_revision;
|
|
1174
|
+
IF destination_activity_revision IS NULL THEN
|
|
1175
|
+
RAISE EXCEPTION 'fork runtime destination activity counter is unavailable'
|
|
1176
|
+
USING ERRCODE = '23514';
|
|
1177
|
+
END IF;
|
|
1178
|
+
UPDATE sessions session_value SET
|
|
1179
|
+
activity_revision = destination_activity_revision,
|
|
1180
|
+
activity_revision_pending_xid = NULL
|
|
1181
|
+
WHERE session_value.account_id = p_account_id
|
|
1182
|
+
AND session_value.workspace_id = p_workspace_id
|
|
1183
|
+
AND session_value.id = p_destination_session_id
|
|
1184
|
+
AND session_value.activity_revision_pending_xid = pg_current_xact_id()::text::bigint;
|
|
1185
|
+
IF NOT FOUND THEN
|
|
1186
|
+
RAISE EXCEPTION 'fork runtime destination activity was not finalized'
|
|
1187
|
+
USING ERRCODE = '23514';
|
|
1188
|
+
END IF;
|
|
1189
|
+
SET CONSTRAINTS sessions_activity_insert_commit_guard,
|
|
1190
|
+
sessions_activity_update_commit_guard IMMEDIATE;
|
|
1191
|
+
PERFORM set_config('opengeni.session_activity_gate_state', '', true);
|
|
1192
|
+
PERFORM set_config('opengeni.session_activity_gate_workspace_id', '', true);
|
|
1193
|
+
RETURN NEXT;
|
|
1194
|
+
EXCEPTION WHEN OTHERS THEN
|
|
1195
|
+
PERFORM set_config(
|
|
1196
|
+
'opengeni.session_activity_gate_state', coalesce(previous_gate_state, ''), true
|
|
1197
|
+
);
|
|
1198
|
+
PERFORM set_config(
|
|
1199
|
+
'opengeni.session_activity_gate_workspace_id',
|
|
1200
|
+
coalesce(previous_gate_workspace, ''), true
|
|
1201
|
+
);
|
|
1202
|
+
RAISE;
|
|
1203
|
+
END
|
|
1204
|
+
$body$;
|
|
1205
|
+
|
|
1206
|
+
CREATE FUNCTION opengeni_private.read_fork_session_runtime(
|
|
1207
|
+
p_account_id uuid,
|
|
1208
|
+
p_workspace_id uuid,
|
|
1209
|
+
p_source_session_id uuid,
|
|
1210
|
+
p_destination_session_id uuid,
|
|
1211
|
+
p_actor_subject_id text,
|
|
1212
|
+
p_configuration_digest text
|
|
1213
|
+
) RETURNS TABLE (
|
|
1214
|
+
runtime_event_id uuid,
|
|
1215
|
+
runtime_event_sequence integer
|
|
1216
|
+
)
|
|
1217
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
1218
|
+
SET search_path FROM CURRENT
|
|
1219
|
+
AS $body$
|
|
1220
|
+
DECLARE
|
|
1221
|
+
runtime_marker jsonb;
|
|
1222
|
+
BEGIN
|
|
1223
|
+
IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
|
|
1224
|
+
OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
|
|
1225
|
+
OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
|
|
1226
|
+
OR p_configuration_digest !~ '^[0-9a-f]{64}$'
|
|
1227
|
+
THEN
|
|
1228
|
+
RAISE EXCEPTION 'fork runtime replay authority is invalid'
|
|
1229
|
+
USING ERRCODE = '42501';
|
|
1230
|
+
END IF;
|
|
1231
|
+
SELECT session_value.metadata -> 'forkRuntimeConfiguration'
|
|
1232
|
+
INTO runtime_marker
|
|
1233
|
+
FROM sessions session_value
|
|
1234
|
+
WHERE session_value.account_id = p_account_id
|
|
1235
|
+
AND session_value.workspace_id = p_workspace_id
|
|
1236
|
+
AND session_value.id = p_destination_session_id
|
|
1237
|
+
AND session_value.forked_from_session_id = p_source_session_id
|
|
1238
|
+
AND session_value.owner_subject_id = p_actor_subject_id;
|
|
1239
|
+
IF runtime_marker IS NULL THEN
|
|
1240
|
+
RAISE EXCEPTION 'fork runtime configuration marker is unavailable'
|
|
1241
|
+
USING ERRCODE = '23514';
|
|
1242
|
+
END IF;
|
|
1243
|
+
IF runtime_marker ->> 'digest' IS DISTINCT FROM p_configuration_digest THEN
|
|
1244
|
+
RAISE EXCEPTION 'session fork idempotency conflict' USING ERRCODE = '23505';
|
|
1245
|
+
END IF;
|
|
1246
|
+
runtime_event_id := nullif(runtime_marker ->> 'eventId', '')::uuid;
|
|
1247
|
+
runtime_event_sequence := (runtime_marker ->> 'eventSequence')::integer;
|
|
1248
|
+
IF runtime_event_id IS NULL OR runtime_event_sequence IS NULL THEN
|
|
1249
|
+
RAISE EXCEPTION 'fork runtime configuration marker is incomplete'
|
|
1250
|
+
USING ERRCODE = '23514';
|
|
1251
|
+
END IF;
|
|
1252
|
+
RETURN NEXT;
|
|
1253
|
+
END
|
|
1254
|
+
$body$;
|
|
1255
|
+
|
|
1256
|
+
CREATE FUNCTION fork_session_content_with_runtime(
|
|
1257
|
+
p_account_id uuid,
|
|
1258
|
+
p_source_workspace_id uuid,
|
|
1259
|
+
p_source_session_id uuid,
|
|
1260
|
+
p_actor_subject_id text,
|
|
1261
|
+
p_destination_workspace_id uuid,
|
|
1262
|
+
p_destination_visibility text,
|
|
1263
|
+
p_workspace_shared_acknowledged boolean,
|
|
1264
|
+
p_operation_key text,
|
|
1265
|
+
p_canonical_request_hash text,
|
|
1266
|
+
p_activation_version integer,
|
|
1267
|
+
p_variable_set_ids jsonb,
|
|
1268
|
+
p_rig_id uuid,
|
|
1269
|
+
p_rig_version_id uuid,
|
|
1270
|
+
p_configuration_digest text
|
|
1271
|
+
) RETURNS TABLE (
|
|
1272
|
+
operation_id uuid,
|
|
1273
|
+
event_id uuid,
|
|
1274
|
+
event_sequence integer,
|
|
1275
|
+
session_id uuid,
|
|
1276
|
+
workspace_id uuid,
|
|
1277
|
+
visibility text,
|
|
1278
|
+
authority_epoch integer,
|
|
1279
|
+
copied_history_item_count integer,
|
|
1280
|
+
replay boolean,
|
|
1281
|
+
runtime_event_id uuid,
|
|
1282
|
+
runtime_event_sequence integer
|
|
1283
|
+
)
|
|
1284
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
1285
|
+
SET search_path FROM CURRENT
|
|
1286
|
+
AS $body$
|
|
1287
|
+
BEGIN
|
|
1288
|
+
SELECT fork_result.operation_id, fork_result.event_id, fork_result.event_sequence,
|
|
1289
|
+
fork_result.session_id, fork_result.workspace_id, fork_result.visibility,
|
|
1290
|
+
fork_result.authority_epoch, fork_result.copied_history_item_count, fork_result.replay
|
|
1291
|
+
INTO operation_id, event_id, event_sequence, session_id, workspace_id, visibility,
|
|
1292
|
+
authority_epoch, copied_history_item_count, replay
|
|
1293
|
+
FROM fork_session_content(
|
|
1294
|
+
p_account_id, p_source_workspace_id, p_source_session_id, p_actor_subject_id,
|
|
1295
|
+
p_destination_workspace_id, p_destination_visibility,
|
|
1296
|
+
p_workspace_shared_acknowledged, p_operation_key, p_canonical_request_hash,
|
|
1297
|
+
p_activation_version
|
|
1298
|
+
) fork_result;
|
|
1299
|
+
IF session_id IS NULL THEN
|
|
1300
|
+
RAISE EXCEPTION 'session fork returned no destination' USING ERRCODE = '23514';
|
|
1301
|
+
END IF;
|
|
1302
|
+
SELECT configured.runtime_event_id, configured.runtime_event_sequence
|
|
1303
|
+
INTO runtime_event_id, runtime_event_sequence
|
|
1304
|
+
FROM opengeni_private.configure_fork_session_runtime(
|
|
1305
|
+
p_account_id, p_source_workspace_id, p_source_session_id, session_id,
|
|
1306
|
+
p_actor_subject_id, p_variable_set_ids, p_rig_id, p_rig_version_id,
|
|
1307
|
+
p_configuration_digest
|
|
1308
|
+
) configured;
|
|
1309
|
+
RETURN NEXT;
|
|
1310
|
+
END
|
|
1311
|
+
$body$;
|
|
1312
|
+
|
|
1313
|
+
CREATE FUNCTION replay_applied_session_fork_with_runtime(
|
|
1314
|
+
p_account_id uuid,
|
|
1315
|
+
p_source_workspace_id uuid,
|
|
1316
|
+
p_source_session_id uuid,
|
|
1317
|
+
p_actor_subject_id text,
|
|
1318
|
+
p_destination_workspace_id uuid,
|
|
1319
|
+
p_destination_visibility text,
|
|
1320
|
+
p_workspace_shared_acknowledged boolean,
|
|
1321
|
+
p_operation_key text,
|
|
1322
|
+
p_canonical_request_hash text,
|
|
1323
|
+
p_activation_version integer,
|
|
1324
|
+
p_configuration_digest text
|
|
1325
|
+
) RETURNS TABLE (
|
|
1326
|
+
operation_id uuid,
|
|
1327
|
+
event_id uuid,
|
|
1328
|
+
event_sequence integer,
|
|
1329
|
+
session_id uuid,
|
|
1330
|
+
workspace_id uuid,
|
|
1331
|
+
visibility text,
|
|
1332
|
+
authority_epoch integer,
|
|
1333
|
+
copied_history_item_count integer,
|
|
1334
|
+
replay boolean,
|
|
1335
|
+
runtime_event_id uuid,
|
|
1336
|
+
runtime_event_sequence integer
|
|
1337
|
+
)
|
|
1338
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
1339
|
+
SET search_path FROM CURRENT
|
|
1340
|
+
AS $body$
|
|
1341
|
+
BEGIN
|
|
1342
|
+
SELECT fork_result.operation_id, fork_result.event_id, fork_result.event_sequence,
|
|
1343
|
+
fork_result.session_id, fork_result.workspace_id, fork_result.visibility,
|
|
1344
|
+
fork_result.authority_epoch, fork_result.copied_history_item_count, fork_result.replay
|
|
1345
|
+
INTO operation_id, event_id, event_sequence, session_id, workspace_id, visibility,
|
|
1346
|
+
authority_epoch, copied_history_item_count, replay
|
|
1347
|
+
FROM replay_applied_session_fork(
|
|
1348
|
+
p_account_id, p_source_workspace_id, p_source_session_id, p_actor_subject_id,
|
|
1349
|
+
p_destination_workspace_id, p_destination_visibility,
|
|
1350
|
+
p_workspace_shared_acknowledged, p_operation_key, p_canonical_request_hash,
|
|
1351
|
+
p_activation_version
|
|
1352
|
+
) fork_result;
|
|
1353
|
+
IF session_id IS NULL THEN
|
|
1354
|
+
RETURN;
|
|
1355
|
+
END IF;
|
|
1356
|
+
SELECT configured.runtime_event_id, configured.runtime_event_sequence
|
|
1357
|
+
INTO runtime_event_id, runtime_event_sequence
|
|
1358
|
+
FROM opengeni_private.read_fork_session_runtime(
|
|
1359
|
+
p_account_id, p_source_workspace_id, p_source_session_id, session_id,
|
|
1360
|
+
p_actor_subject_id, p_configuration_digest
|
|
1361
|
+
) configured;
|
|
1362
|
+
RETURN NEXT;
|
|
1363
|
+
END
|
|
1364
|
+
$body$;
|
|
1365
|
+
|
|
1366
|
+
DO $fork_runtime_search_path_and_grants$
|
|
1367
|
+
DECLARE
|
|
1368
|
+
data_schema text := current_schema();
|
|
1369
|
+
application_role text;
|
|
1370
|
+
BEGIN
|
|
1371
|
+
EXECUTE format(
|
|
1372
|
+
'ALTER FUNCTION opengeni_private.configure_fork_session_runtime('
|
|
1373
|
+
|| 'uuid,uuid,uuid,uuid,text,jsonb,uuid,uuid,text) '
|
|
1374
|
+
|| 'SET search_path = pg_catalog, %I, pg_temp',
|
|
1375
|
+
data_schema
|
|
1376
|
+
);
|
|
1377
|
+
EXECUTE format(
|
|
1378
|
+
'ALTER FUNCTION opengeni_private.read_fork_session_runtime('
|
|
1379
|
+
|| 'uuid,uuid,uuid,uuid,text,text) '
|
|
1380
|
+
|| 'SET search_path = pg_catalog, %I, pg_temp',
|
|
1381
|
+
data_schema
|
|
1382
|
+
);
|
|
1383
|
+
EXECUTE format(
|
|
1384
|
+
'ALTER FUNCTION %I.fork_session_content_with_runtime('
|
|
1385
|
+
|| 'uuid,uuid,uuid,text,uuid,text,boolean,text,text,integer,jsonb,uuid,uuid,text) '
|
|
1386
|
+
|| 'SET search_path = pg_catalog, %I, pg_temp',
|
|
1387
|
+
data_schema, data_schema
|
|
1388
|
+
);
|
|
1389
|
+
EXECUTE format(
|
|
1390
|
+
'ALTER FUNCTION %I.replay_applied_session_fork_with_runtime('
|
|
1391
|
+
|| 'uuid,uuid,uuid,text,uuid,text,boolean,text,text,integer,text) '
|
|
1392
|
+
|| 'SET search_path = pg_catalog, %I, pg_temp',
|
|
1393
|
+
data_schema, data_schema
|
|
1394
|
+
);
|
|
1395
|
+
FOR application_role IN
|
|
1396
|
+
SELECT role_value.rolname
|
|
1397
|
+
FROM jsonb_array_elements_text(
|
|
1398
|
+
current_setting('opengeni.migration_application_roles')::jsonb
|
|
1399
|
+
) configured(value)
|
|
1400
|
+
JOIN pg_catalog.pg_roles role_value
|
|
1401
|
+
ON role_value.rolname = configured.value
|
|
1402
|
+
LOOP
|
|
1403
|
+
EXECUTE format(
|
|
1404
|
+
'GRANT EXECUTE ON FUNCTION %I.fork_session_content_with_runtime('
|
|
1405
|
+
|| 'uuid,uuid,uuid,text,uuid,text,boolean,text,text,integer,jsonb,uuid,uuid,text) '
|
|
1406
|
+
|| 'TO %I',
|
|
1407
|
+
data_schema, application_role
|
|
1408
|
+
);
|
|
1409
|
+
EXECUTE format(
|
|
1410
|
+
'GRANT EXECUTE ON FUNCTION %I.replay_applied_session_fork_with_runtime('
|
|
1411
|
+
|| 'uuid,uuid,uuid,text,uuid,text,boolean,text,text,integer,text) '
|
|
1412
|
+
|| 'TO %I',
|
|
1413
|
+
data_schema, application_role
|
|
1414
|
+
);
|
|
1415
|
+
END LOOP;
|
|
1416
|
+
END
|
|
1417
|
+
$fork_runtime_search_path_and_grants$;
|
|
1418
|
+
|
|
1419
|
+
REVOKE ALL ON FUNCTION opengeni_private.configure_fork_session_runtime(
|
|
1420
|
+
uuid, uuid, uuid, uuid, text, jsonb, uuid, uuid, text
|
|
1421
|
+
) FROM PUBLIC;
|
|
1422
|
+
REVOKE ALL ON FUNCTION opengeni_private.read_fork_session_runtime(
|
|
1423
|
+
uuid, uuid, uuid, uuid, text, text
|
|
1424
|
+
) FROM PUBLIC;
|
|
1425
|
+
REVOKE ALL ON FUNCTION fork_session_content_with_runtime(
|
|
1426
|
+
uuid, uuid, uuid, text, uuid, text, boolean, text, text, integer,
|
|
1427
|
+
jsonb, uuid, uuid, text
|
|
1428
|
+
) FROM PUBLIC;
|
|
1429
|
+
REVOKE ALL ON FUNCTION replay_applied_session_fork_with_runtime(
|
|
1430
|
+
uuid, uuid, uuid, text, uuid, text, boolean, text, text, integer, text
|
|
1431
|
+
) FROM PUBLIC;
|
|
1432
|
+
|
|
1433
|
+
-- The accepted-turn personal-resource closure must include every explicit
|
|
1434
|
+
-- user-scoped Variable Set, not only the legacy final alias. Patch the exact
|
|
1435
|
+
-- drained function source so the established grant/receipt protocol remains
|
|
1436
|
+
-- byte-for-byte unchanged outside this ordered-selection extension.
|
|
1437
|
+
ALTER TABLE turn_personal_resource_attachment_receipts
|
|
1438
|
+
DROP CONSTRAINT turn_personal_resource_attachment_receipts_identity_chk;
|
|
1439
|
+
ALTER TABLE turn_personal_resource_attachment_receipts
|
|
1440
|
+
ADD CONSTRAINT turn_personal_resource_attachment_receipts_identity_chk CHECK (
|
|
1441
|
+
octet_length(initiating_human_subject_id) BETWEEN 1 AND 512
|
|
1442
|
+
AND membership_authorization_revision > 0
|
|
1443
|
+
AND session_authority_epoch > 0
|
|
1444
|
+
AND grant_mode IN ('once', 'session', 'always')
|
|
1445
|
+
AND session_visibility IN ('user_private', 'workspace_shared')
|
|
1446
|
+
AND shared_output_warning_version = 1
|
|
1447
|
+
AND resource_count BETWEEN 1 AND 52
|
|
1448
|
+
AND (session_visibility <> 'workspace_shared' OR shared_output_acknowledged IS TRUE)
|
|
1449
|
+
);
|
|
1450
|
+
ALTER TABLE turn_personal_resource_snapshots
|
|
1451
|
+
DROP CONSTRAINT turn_personal_resource_snapshots_generation_chk;
|
|
1452
|
+
ALTER TABLE turn_personal_resource_snapshots
|
|
1453
|
+
ADD CONSTRAINT turn_personal_resource_snapshots_generation_chk CHECK (
|
|
1454
|
+
membership_authorization_revision > 0
|
|
1455
|
+
AND authority_generation > 0
|
|
1456
|
+
AND grant_generation > 0
|
|
1457
|
+
AND cardinality(selection_sources) BETWEEN 1 AND 50
|
|
1458
|
+
);
|
|
1459
|
+
|
|
1460
|
+
ALTER TABLE session_variable_set_attachments NO FORCE ROW LEVEL SECURITY;
|
|
1461
|
+
ALTER TABLE workspace_variable_sets NO FORCE ROW LEVEL SECURITY;
|
|
1462
|
+
DO $extend_atomic_attachment_function_for_session_sets$
|
|
1463
|
+
DECLARE
|
|
1464
|
+
data_schema text := current_schema();
|
|
1465
|
+
function_source text;
|
|
1466
|
+
prior_source text;
|
|
1467
|
+
BEGIN
|
|
1468
|
+
SELECT procedure_value.prosrc INTO STRICT function_source
|
|
1469
|
+
FROM pg_proc procedure_value
|
|
1470
|
+
JOIN pg_namespace namespace_value ON namespace_value.oid = procedure_value.pronamespace
|
|
1471
|
+
WHERE namespace_value.nspname = data_schema
|
|
1472
|
+
AND procedure_value.oid = (
|
|
1473
|
+
pg_catalog.format(
|
|
1474
|
+
'%I.accept_turn_personal_resource_attachment(uuid,uuid,uuid,uuid,text,integer,boolean,integer)',
|
|
1475
|
+
data_schema
|
|
1476
|
+
)::regprocedure
|
|
1477
|
+
);
|
|
1478
|
+
|
|
1479
|
+
prior_source := function_source;
|
|
1480
|
+
function_source := replace(function_source,
|
|
1481
|
+
$old$ PERFORM 1 FROM workspace_variable_sets variable_set
|
|
1482
|
+
WHERE variable_set.id = session_row.variable_set_id
|
|
1483
|
+
AND variable_set.account_id = p_account_id
|
|
1484
|
+
FOR SHARE;$old$,
|
|
1485
|
+
$new$ PERFORM variable_set.id
|
|
1486
|
+
FROM session_variable_set_attachments attachment
|
|
1487
|
+
JOIN workspace_variable_sets variable_set
|
|
1488
|
+
ON variable_set.id = attachment.variable_set_id
|
|
1489
|
+
AND variable_set.account_id = p_account_id
|
|
1490
|
+
WHERE attachment.account_id = p_account_id
|
|
1491
|
+
AND attachment.workspace_id = p_workspace_id
|
|
1492
|
+
AND attachment.session_id = p_session_id
|
|
1493
|
+
ORDER BY attachment.position
|
|
1494
|
+
FOR SHARE OF variable_set;$new$
|
|
1495
|
+
);
|
|
1496
|
+
IF function_source = prior_source THEN
|
|
1497
|
+
RAISE EXCEPTION '0352 could not extend the personal-resource attachment lock set'
|
|
1498
|
+
USING ERRCODE = '55000';
|
|
1499
|
+
END IF;
|
|
1500
|
+
|
|
1501
|
+
prior_source := function_source;
|
|
1502
|
+
function_source := replace(function_source,
|
|
1503
|
+
$old$ FROM workspace_variable_sets variable_set
|
|
1504
|
+
WHERE variable_set.id = session_row.variable_set_id
|
|
1505
|
+
AND variable_set.account_id = p_account_id
|
|
1506
|
+
AND variable_set.authority_scope = 'user'$old$,
|
|
1507
|
+
$new$ FROM session_variable_set_attachments attachment
|
|
1508
|
+
JOIN workspace_variable_sets variable_set
|
|
1509
|
+
ON variable_set.id = attachment.variable_set_id
|
|
1510
|
+
AND variable_set.account_id = p_account_id
|
|
1511
|
+
AND variable_set.authority_scope = 'user'
|
|
1512
|
+
WHERE attachment.account_id = p_account_id
|
|
1513
|
+
AND attachment.workspace_id = p_workspace_id
|
|
1514
|
+
AND attachment.session_id = p_session_id$new$
|
|
1515
|
+
);
|
|
1516
|
+
IF function_source = prior_source THEN
|
|
1517
|
+
RAISE EXCEPTION '0352 could not extend the personal-resource attachment closure'
|
|
1518
|
+
USING ERRCODE = '55000';
|
|
1519
|
+
END IF;
|
|
1520
|
+
function_source := replace(
|
|
1521
|
+
function_source,
|
|
1522
|
+
'''session_variable_set''::text AS selection_source',
|
|
1523
|
+
'''session_variable_set:'' || attachment.position::text AS selection_source'
|
|
1524
|
+
);
|
|
1525
|
+
function_source := replace(function_source, 'IF selected_count > 28 THEN',
|
|
1526
|
+
'IF selected_count > 52 THEN');
|
|
1527
|
+
IF function_source NOT LIKE '%IF selected_count > 52 THEN%'
|
|
1528
|
+
OR function_source LIKE '%IF selected_count > 28 THEN%'
|
|
1529
|
+
THEN
|
|
1530
|
+
RAISE EXCEPTION '0352 could not update the personal-resource attachment bound'
|
|
1531
|
+
USING ERRCODE = '55000';
|
|
1532
|
+
END IF;
|
|
1533
|
+
|
|
1534
|
+
EXECUTE pg_catalog.format(
|
|
1535
|
+
'CREATE OR REPLACE FUNCTION %I.accept_turn_personal_resource_attachment('
|
|
1536
|
+
|| 'p_account_id uuid, p_workspace_id uuid, p_session_id uuid, p_turn_id uuid, '
|
|
1537
|
+
|| 'p_mode text, p_expected_authority_epoch integer, '
|
|
1538
|
+
|| 'p_workspace_shared_acknowledged boolean, '
|
|
1539
|
+
|| 'p_shared_output_warning_version integer) '
|
|
1540
|
+
|| 'RETURNS TABLE (grant_mode text, grant_context text, resource_count integer, '
|
|
1541
|
+
|| 'resource_kinds text[], shared_output_warning_version integer, replay boolean) '
|
|
1542
|
+
|| 'LANGUAGE plpgsql SECURITY DEFINER '
|
|
1543
|
+
|| 'SET search_path TO pg_catalog, %I, pg_temp AS %L',
|
|
1544
|
+
data_schema, data_schema, function_source
|
|
1545
|
+
);
|
|
1546
|
+
END
|
|
1547
|
+
$extend_atomic_attachment_function_for_session_sets$;
|
|
1548
|
+
ALTER TABLE workspace_variable_sets FORCE ROW LEVEL SECURITY;
|
|
1549
|
+
ALTER TABLE session_variable_set_attachments FORCE ROW LEVEL SECURITY;
|
|
1550
|
+
|
|
1551
|
+
-- Scheduled attempts freeze every explicit set generation. Extend the existing
|
|
1552
|
+
-- attempt oracle to consume the new plural accepted snapshot while retaining
|
|
1553
|
+
-- the legacy singular fallback for pre-0344 runs.
|
|
1554
|
+
DO $extend_scheduled_variable_set_generation_oracle$
|
|
1555
|
+
DECLARE
|
|
1556
|
+
data_schema text := current_schema();
|
|
1557
|
+
function_oid regprocedure;
|
|
1558
|
+
function_source text;
|
|
1559
|
+
prior_source text;
|
|
1560
|
+
BEGIN
|
|
1561
|
+
function_oid := pg_catalog.to_regprocedure(
|
|
1562
|
+
pg_catalog.format(
|
|
1563
|
+
'%I.scheduled_variable_set_expected_generation_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid)',
|
|
1564
|
+
data_schema
|
|
1565
|
+
)
|
|
1566
|
+
);
|
|
1567
|
+
IF function_oid IS NULL THEN
|
|
1568
|
+
-- Some focused legacy-migration tests deliberately apply a sparse suffix
|
|
1569
|
+
-- that predates the scheduled-attempt oracle. There is nothing to extend
|
|
1570
|
+
-- in that shape; full migration chains always have the function here.
|
|
1571
|
+
RETURN;
|
|
1572
|
+
END IF;
|
|
1573
|
+
|
|
1574
|
+
SELECT procedure_value.prosrc INTO STRICT function_source
|
|
1575
|
+
FROM pg_proc procedure_value
|
|
1576
|
+
JOIN pg_namespace namespace_value ON namespace_value.oid = procedure_value.pronamespace
|
|
1577
|
+
WHERE namespace_value.nspname = data_schema
|
|
1578
|
+
AND procedure_value.oid = function_oid;
|
|
1579
|
+
|
|
1580
|
+
prior_source := function_source;
|
|
1581
|
+
function_source := replace(
|
|
1582
|
+
function_source,
|
|
1583
|
+
'session_value.variable_set_id, session_value.rig_id,',
|
|
1584
|
+
'session_value.variable_set_ids, session_value.variable_set_id, session_value.rig_id,'
|
|
1585
|
+
);
|
|
1586
|
+
IF function_source = prior_source THEN
|
|
1587
|
+
RAISE EXCEPTION '0352 could not extend the scheduled session selection snapshot'
|
|
1588
|
+
USING ERRCODE = '55000';
|
|
1589
|
+
END IF;
|
|
1590
|
+
|
|
1591
|
+
prior_source := function_source;
|
|
1592
|
+
function_source := replace(function_source,
|
|
1593
|
+
$old$ SELECT (run_snapshot -> 'targetSessionExecution' ->> 'variableSetGeneration')::bigint
|
|
1594
|
+
WHERE run_snapshot -> 'targetSessionExecution' <> 'null'::jsonb
|
|
1595
|
+
AND run_snapshot -> 'targetSessionExecution' ->> 'variableSetId' = p_variable_set_id::text$old$,
|
|
1596
|
+
$new$ SELECT (item ->> 'generation')::bigint
|
|
1597
|
+
FROM jsonb_array_elements(coalesce(
|
|
1598
|
+
run_snapshot -> 'targetSessionExecution' -> 'variableSets', '[]'::jsonb
|
|
1599
|
+
)) item
|
|
1600
|
+
WHERE item ->> 'id' = p_variable_set_id::text
|
|
1601
|
+
UNION ALL
|
|
1602
|
+
SELECT (run_snapshot -> 'targetSessionExecution' ->> 'variableSetGeneration')::bigint
|
|
1603
|
+
WHERE run_snapshot -> 'targetSessionExecution' <> 'null'::jsonb
|
|
1604
|
+
AND jsonb_array_length(coalesce(
|
|
1605
|
+
run_snapshot -> 'targetSessionExecution' -> 'variableSets', '[]'::jsonb
|
|
1606
|
+
)) = 0
|
|
1607
|
+
AND run_snapshot -> 'targetSessionExecution' ->> 'variableSetId' = p_variable_set_id::text$new$
|
|
1608
|
+
);
|
|
1609
|
+
IF function_source = prior_source THEN
|
|
1610
|
+
RAISE EXCEPTION '0352 could not extend the scheduled accepted generation lookup'
|
|
1611
|
+
USING ERRCODE = '55000';
|
|
1612
|
+
END IF;
|
|
1613
|
+
|
|
1614
|
+
prior_source := function_source;
|
|
1615
|
+
function_source := replace(
|
|
1616
|
+
function_source,
|
|
1617
|
+
'IF session_row.variable_set_id IS DISTINCT FROM p_variable_set_id',
|
|
1618
|
+
'IF NOT coalesce(session_row.variable_set_ids, ''[]''::jsonb) ? p_variable_set_id::text'
|
|
1619
|
+
);
|
|
1620
|
+
IF function_source = prior_source THEN
|
|
1621
|
+
RAISE EXCEPTION '0352 could not extend the scheduled exact-session selection check'
|
|
1622
|
+
USING ERRCODE = '55000';
|
|
1623
|
+
END IF;
|
|
1624
|
+
|
|
1625
|
+
EXECUTE pg_catalog.format(
|
|
1626
|
+
'CREATE OR REPLACE FUNCTION %I.scheduled_variable_set_expected_generation_for_attempt('
|
|
1627
|
+
|| 'p_account_id uuid, p_workspace_id uuid, p_session_id uuid, p_turn_id uuid, '
|
|
1628
|
+
|| 'p_attempt_id uuid, p_execution_generation integer, p_variable_set_id uuid) '
|
|
1629
|
+
|| 'RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER '
|
|
1630
|
+
|| 'SET search_path TO pg_catalog, %I, pg_temp AS %L',
|
|
1631
|
+
data_schema, data_schema, function_source
|
|
1632
|
+
);
|
|
1633
|
+
END
|
|
1634
|
+
$extend_scheduled_variable_set_generation_oracle$;
|
|
1635
|
+
|
|
1636
|
+
DO $grants$
|
|
1637
|
+
DECLARE
|
|
1638
|
+
data_schema text := current_schema();
|
|
1639
|
+
configured_roles jsonb := current_setting(
|
|
1640
|
+
'opengeni.migration_application_roles', false
|
|
1641
|
+
)::jsonb;
|
|
1642
|
+
application_role text;
|
|
1643
|
+
BEGIN
|
|
1644
|
+
FOR application_role IN
|
|
1645
|
+
SELECT roles.role_name
|
|
1646
|
+
FROM jsonb_array_elements_text(configured_roles) roles(role_name)
|
|
1647
|
+
JOIN pg_catalog.pg_roles role_value ON role_value.rolname = roles.role_name
|
|
1648
|
+
LOOP
|
|
1649
|
+
EXECUTE pg_catalog.format(
|
|
1650
|
+
'REVOKE ALL ON TABLE %I.session_variable_set_attachments FROM %I',
|
|
1651
|
+
data_schema,
|
|
1652
|
+
application_role
|
|
1653
|
+
);
|
|
1654
|
+
END LOOP;
|
|
1655
|
+
END
|
|
1656
|
+
$grants$;
|
|
1657
|
+
|
|
1658
|
+
COMMENT ON COLUMN sessions.variable_set_ids IS
|
|
1659
|
+
'Ordered low-to-high precedence Variable Set ids; the final entry is mirrored in legacy variable_set_id.';
|
|
1660
|
+
COMMENT ON TABLE session_variable_set_attachments IS
|
|
1661
|
+
'FK-backed lifecycle-only projection of ordered session Variable Set selection; values never appear here.';
|