@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.
- package/dist/browser-auth.d.ts +209 -1
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +11 -2
- package/dist/browser-sessions.d.ts +83 -1
- package/dist/browser-state-artifacts.d.ts +75 -1
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-integrations.d.ts +8 -6
- package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/chunk-FPVAD4CO.js +117 -0
- package/dist/chunk-FPVAD4CO.js.map +1 -0
- package/dist/chunk-GYO7ED2Q.js +259 -0
- package/dist/chunk-GYO7ED2Q.js.map +1 -0
- package/dist/{chunk-F5FT7BEZ.js → chunk-HSOWSYOX.js} +2 -2
- package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
- package/dist/chunk-VKLB2ZGI.js.map +1 -0
- package/dist/{chunk-NBE6CHRI.js → chunk-VTPRIXKG.js} +49 -9
- package/dist/chunk-VTPRIXKG.js.map +1 -0
- package/dist/{chunk-ISI2TIUG.js → chunk-Z7VYMNPZ.js} +76 -4
- package/dist/chunk-Z7VYMNPZ.js.map +1 -0
- package/dist/database.d.ts +5 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +467 -29
- package/dist/index.js +23247 -16232
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +23 -7
- package/dist/integration-bindings.d.ts +24 -24
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +11 -0
- package/dist/interaction-schema.d.ts +698 -34
- package/dist/persistence-errors.d.ts +8 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +8 -7
- package/dist/schema.d.ts +9464 -5412
- package/dist/schema.js +51 -9
- package/dist/session-control.d.ts +29 -3
- package/dist/session-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/video-generation.d.ts +9 -0
- package/dist/video-generation.js +5 -3
- package/dist/xai-subscription.d.ts +326 -0
- package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_recover_unclaimed_session_turns.sql +307 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +13 -5
- package/src/browser-auth.ts +2600 -123
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +220 -3
- package/src/browser-sessions.ts +334 -45
- package/src/browser-state-artifacts.ts +402 -12
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-integrations.ts +121 -213
- package/src/database.ts +41 -1
- package/src/index.ts +4625 -411
- package/src/insights.ts +109 -28
- package/src/integration-bindings.ts +101 -101
- package/src/{integration-features.ts → integration-facets.ts} +187 -184
- package/src/interaction-revisions.ts +33 -3
- package/src/interaction-schema.ts +311 -8
- package/src/pack-components.ts +43 -43
- package/src/pack-installations.ts +0 -43
- package/src/persistence-errors.ts +60 -1
- package/src/plugin-packages.ts +9 -9
- package/src/provision-roles.ts +232 -0
- package/src/runtime-posture.ts +227 -14
- package/src/schema.ts +1188 -75
- package/src/session-control.ts +122 -37
- package/src/session-queue-commands.ts +78 -1
- package/src/session-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +9 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +90 -12
- package/src/workspace-artifacts.ts +1 -1
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-ISI2TIUG.js.map +0 -1
- package/dist/chunk-MWDVXQOL.js.map +0 -1
- package/dist/chunk-NBE6CHRI.js.map +0 -1
- package/dist/chunk-YHZXQ7HP.js.map +0 -1
- package/dist/integration-features.d.ts +0 -99
- /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.';
|