@opengeni/db 0.10.7 → 0.12.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/{chunk-P6PKXY5W.js → chunk-VUKRIBO5.js} +485 -12
- package/dist/chunk-VUKRIBO5.js.map +1 -0
- package/dist/{chunk-KW526IJA.js → chunk-Y5WZZVQK.js} +80 -4
- package/dist/chunk-Y5WZZVQK.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5114 -2085
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +6 -3
- package/dist/migrate.js +1 -1
- package/dist/provision-roles.d.ts +720 -63
- package/dist/{schema-CqkzrBRS.d.ts → schema-CnpD6BcX.d.ts} +4684 -2913
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +17 -1
- package/drizzle/0109_nested_agent_depth_expand.sql +42 -0
- package/drizzle/0110_nested_agent_depth_boundary.sql +480 -0
- package/drizzle/0111_nested_agent_depth_backfill.sql +49 -0
- package/drizzle/0112_nested_agent_depth_contract.sql +38 -0
- package/drizzle/0113_nested_agent_depth_validate.sql +13 -0
- package/drizzle/0114_nested_agent_depth_contract.sql +49 -0
- package/drizzle/0115_nested_agent_depth_validate.sql +11 -0
- package/drizzle/0116_nested_agent_depth_index.sql +4 -0
- package/drizzle/0117_sandbox_recovery_generations.sql +699 -0
- package/drizzle/0118_new_session_drafts.sql +59 -0
- package/drizzle/0119_pending_tool_output_policy.sql +5 -0
- package/drizzle/0120_durable_goal_wake.sql +360 -0
- package/drizzle/0121_goal_update_idempotency.sql +11 -0
- package/package.json +3 -3
- package/src/index.ts +5961 -1240
- package/src/migrate.ts +131 -2
- package/src/new-session-drafts.ts +144 -0
- package/src/schema.ts +519 -15
- package/src/session-control.ts +42 -18
- package/src/session-tool-call-settlement.ts +6 -1
- package/dist/chunk-KW526IJA.js.map +0 -1
- package/dist/chunk-P6PKXY5W.js.map +0 -1
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Activate the durable workspace-generation, mutation-admission, and
|
|
3
|
+
-- retained-process protocol. Old API/control/turn workers MUST be stopped: they
|
|
4
|
+
-- cannot name exact direct/process authority and do not set the v2 marker.
|
|
5
|
+
|
|
6
|
+
SET LOCAL lock_timeout = '5s';
|
|
7
|
+
SET LOCAL statement_timeout = '30min';
|
|
8
|
+
|
|
9
|
+
-- Reject an obviously live old application before waiting on table locks. The
|
|
10
|
+
-- same guard runs again after the locks to close the connect/write race.
|
|
11
|
+
DO $maintenance_preflight_guard$
|
|
12
|
+
BEGIN
|
|
13
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
|
|
14
|
+
AND EXISTS (
|
|
15
|
+
SELECT 1
|
|
16
|
+
FROM pg_stat_activity
|
|
17
|
+
WHERE datname = current_database()
|
|
18
|
+
AND usename = 'opengeni_app'
|
|
19
|
+
AND pid <> pg_backend_pid()
|
|
20
|
+
)
|
|
21
|
+
THEN
|
|
22
|
+
RAISE EXCEPTION
|
|
23
|
+
'sandbox recovery protocol v2 activation requires all opengeni_app sessions to be stopped'
|
|
24
|
+
USING ERRCODE = '55000';
|
|
25
|
+
END IF;
|
|
26
|
+
END
|
|
27
|
+
$maintenance_preflight_guard$;
|
|
28
|
+
|
|
29
|
+
-- Serialize the one-way cutover against every old lease/holder/PTY writer. The
|
|
30
|
+
-- migration runner wraps this file in one transaction, so a timeout or guard
|
|
31
|
+
-- failure leaves no partially visible protocol.
|
|
32
|
+
LOCK TABLE sandbox_leases IN ACCESS EXCLUSIVE MODE;
|
|
33
|
+
LOCK TABLE sandbox_lease_holders IN ACCESS EXCLUSIVE MODE;
|
|
34
|
+
LOCK TABLE sandbox_pty_sessions IN ACCESS EXCLUSIVE MODE;
|
|
35
|
+
|
|
36
|
+
DO $maintenance_guard$
|
|
37
|
+
BEGIN
|
|
38
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
|
|
39
|
+
AND EXISTS (
|
|
40
|
+
SELECT 1
|
|
41
|
+
FROM pg_stat_activity
|
|
42
|
+
WHERE datname = current_database()
|
|
43
|
+
AND usename = 'opengeni_app'
|
|
44
|
+
AND pid <> pg_backend_pid()
|
|
45
|
+
)
|
|
46
|
+
THEN
|
|
47
|
+
RAISE EXCEPTION
|
|
48
|
+
'sandbox recovery protocol v2 activation requires all opengeni_app sessions to be stopped'
|
|
49
|
+
USING ERRCODE = '55000';
|
|
50
|
+
END IF;
|
|
51
|
+
END
|
|
52
|
+
$maintenance_guard$;
|
|
53
|
+
|
|
54
|
+
-- One exact monotonic write set per lease. Generation zero is the legacy/clean
|
|
55
|
+
-- baseline; a verified archive is complete only when archive_generation equals
|
|
56
|
+
-- workspace_generation. Missing or older generations remain truthfully stale.
|
|
57
|
+
ALTER TABLE sandbox_leases
|
|
58
|
+
ADD COLUMN IF NOT EXISTS workspace_generation integer NOT NULL DEFAULT 0,
|
|
59
|
+
ADD COLUMN IF NOT EXISTS archive_generation integer;
|
|
60
|
+
|
|
61
|
+
ALTER TABLE sandbox_leases
|
|
62
|
+
DROP CONSTRAINT IF EXISTS sandbox_leases_workspace_generation_check,
|
|
63
|
+
DROP CONSTRAINT IF EXISTS sandbox_leases_archive_generation_check;
|
|
64
|
+
ALTER TABLE sandbox_leases
|
|
65
|
+
ADD CONSTRAINT sandbox_leases_workspace_generation_check
|
|
66
|
+
CHECK (workspace_generation >= 0),
|
|
67
|
+
ADD CONSTRAINT sandbox_leases_archive_generation_check
|
|
68
|
+
CHECK (
|
|
69
|
+
archive_generation IS NULL
|
|
70
|
+
OR (archive_generation >= 0 AND archive_generation <= workspace_generation)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
-- Composite identities prevent a globally valid UUID from crossing tenant,
|
|
74
|
+
-- workspace, group, session, or lease scope through a malformed internal write.
|
|
75
|
+
CREATE UNIQUE INDEX IF NOT EXISTS sandbox_leases_scoped_id_uq
|
|
76
|
+
ON sandbox_leases (account_id, workspace_id, sandbox_group_id, id);
|
|
77
|
+
-- sandbox_leases' primary key already makes id unique; this narrower composite
|
|
78
|
+
-- exists solely as the referenced tenant/workspace key for holders.
|
|
79
|
+
CREATE UNIQUE INDEX IF NOT EXISTS sandbox_leases_account_workspace_id_uq
|
|
80
|
+
ON sandbox_leases (account_id, workspace_id, id);
|
|
81
|
+
|
|
82
|
+
ALTER TABLE sandbox_lease_holders
|
|
83
|
+
DROP CONSTRAINT IF EXISTS sandbox_lease_holders_kind_check;
|
|
84
|
+
ALTER TABLE sandbox_lease_holders
|
|
85
|
+
ADD CONSTRAINT sandbox_lease_holders_kind_check
|
|
86
|
+
CHECK (kind IN ('turn', 'viewer', 'direct', 'process'));
|
|
87
|
+
|
|
88
|
+
ALTER TABLE sandbox_lease_holders
|
|
89
|
+
DROP CONSTRAINT IF EXISTS sandbox_lease_holders_lease_scope_fk;
|
|
90
|
+
ALTER TABLE sandbox_lease_holders
|
|
91
|
+
ADD CONSTRAINT sandbox_lease_holders_lease_scope_fk
|
|
92
|
+
FOREIGN KEY (account_id, workspace_id, lease_id)
|
|
93
|
+
REFERENCES sandbox_leases (account_id, workspace_id, id)
|
|
94
|
+
ON DELETE CASCADE;
|
|
95
|
+
|
|
96
|
+
CREATE TABLE sandbox_workspace_mutation_admissions (
|
|
97
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
98
|
+
account_id uuid NOT NULL,
|
|
99
|
+
workspace_id uuid NOT NULL,
|
|
100
|
+
lease_id uuid NOT NULL,
|
|
101
|
+
sandbox_group_id uuid NOT NULL,
|
|
102
|
+
session_id uuid NOT NULL,
|
|
103
|
+
actor_kind text NOT NULL,
|
|
104
|
+
actor_id uuid NOT NULL,
|
|
105
|
+
turn_id uuid,
|
|
106
|
+
attempt_id uuid,
|
|
107
|
+
execution_generation integer,
|
|
108
|
+
holder_kind text NOT NULL,
|
|
109
|
+
holder_id text NOT NULL,
|
|
110
|
+
lease_epoch integer NOT NULL,
|
|
111
|
+
provider_backend text NOT NULL,
|
|
112
|
+
provider_instance_id text NOT NULL,
|
|
113
|
+
route_kind text NOT NULL,
|
|
114
|
+
route_target_id uuid,
|
|
115
|
+
route_epoch integer NOT NULL,
|
|
116
|
+
workspace_generation integer NOT NULL,
|
|
117
|
+
operation text NOT NULL,
|
|
118
|
+
provider_outcome text,
|
|
119
|
+
admitted_at timestamptz NOT NULL DEFAULT now(),
|
|
120
|
+
settled_at timestamptz,
|
|
121
|
+
|
|
122
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_workspace_account_fk
|
|
123
|
+
FOREIGN KEY (workspace_id, account_id)
|
|
124
|
+
REFERENCES workspaces (id, account_id) ON DELETE CASCADE,
|
|
125
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_workspace_session_fk
|
|
126
|
+
FOREIGN KEY (workspace_id, session_id)
|
|
127
|
+
REFERENCES sessions (workspace_id, id) ON DELETE RESTRICT,
|
|
128
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_workspace_turn_fk
|
|
129
|
+
FOREIGN KEY (workspace_id, turn_id)
|
|
130
|
+
REFERENCES session_turns (workspace_id, id) ON DELETE RESTRICT,
|
|
131
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_workspace_attempt_fk
|
|
132
|
+
FOREIGN KEY (workspace_id, attempt_id)
|
|
133
|
+
REFERENCES session_turn_attempts (workspace_id, id) ON DELETE RESTRICT,
|
|
134
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_lease_scope_fk
|
|
135
|
+
FOREIGN KEY (account_id, workspace_id, sandbox_group_id, lease_id)
|
|
136
|
+
REFERENCES sandbox_leases (account_id, workspace_id, sandbox_group_id, id)
|
|
137
|
+
ON DELETE CASCADE,
|
|
138
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_generation_check
|
|
139
|
+
CHECK (
|
|
140
|
+
workspace_generation > 0
|
|
141
|
+
AND lease_epoch >= 0
|
|
142
|
+
AND route_epoch >= 0
|
|
143
|
+
AND (execution_generation IS NULL OR execution_generation > 0)
|
|
144
|
+
),
|
|
145
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_actor_check
|
|
146
|
+
CHECK (
|
|
147
|
+
(
|
|
148
|
+
actor_kind = 'turn'
|
|
149
|
+
AND actor_id = attempt_id
|
|
150
|
+
AND turn_id IS NOT NULL
|
|
151
|
+
AND attempt_id IS NOT NULL
|
|
152
|
+
AND execution_generation IS NOT NULL
|
|
153
|
+
AND holder_kind = 'turn'
|
|
154
|
+
) OR (
|
|
155
|
+
actor_kind = 'direct'
|
|
156
|
+
AND turn_id IS NULL
|
|
157
|
+
AND attempt_id IS NULL
|
|
158
|
+
AND execution_generation IS NULL
|
|
159
|
+
AND holder_kind = 'direct'
|
|
160
|
+
) OR (
|
|
161
|
+
actor_kind = 'process'
|
|
162
|
+
AND turn_id IS NULL
|
|
163
|
+
AND attempt_id IS NULL
|
|
164
|
+
AND execution_generation IS NULL
|
|
165
|
+
AND holder_kind = 'process'
|
|
166
|
+
)
|
|
167
|
+
),
|
|
168
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_route_check
|
|
169
|
+
CHECK (
|
|
170
|
+
actor_kind IN ('turn', 'direct', 'process')
|
|
171
|
+
AND holder_kind IN ('turn', 'direct', 'process')
|
|
172
|
+
AND octet_length(holder_id) BETWEEN 1 AND 256
|
|
173
|
+
AND octet_length(provider_backend) BETWEEN 1 AND 64
|
|
174
|
+
AND octet_length(provider_instance_id) BETWEEN 1 AND 512
|
|
175
|
+
AND route_kind IN ('home', 'active')
|
|
176
|
+
AND (route_kind = 'active' OR route_target_id IS NULL)
|
|
177
|
+
),
|
|
178
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_operation_check
|
|
179
|
+
CHECK (octet_length(operation) BETWEEN 1 AND 128),
|
|
180
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_outcome_check
|
|
181
|
+
CHECK (provider_outcome IS NULL OR provider_outcome IN ('resolved', 'rejected', 'retained')),
|
|
182
|
+
CONSTRAINT sandbox_workspace_mutation_admissions_settlement_check
|
|
183
|
+
CHECK (
|
|
184
|
+
(provider_outcome IS NULL AND settled_at IS NULL)
|
|
185
|
+
OR (provider_outcome = 'retained' AND settled_at IS NULL)
|
|
186
|
+
OR (provider_outcome IN ('resolved', 'rejected') AND settled_at IS NOT NULL)
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
CREATE UNIQUE INDEX sandbox_workspace_mutation_admissions_lease_generation_uq
|
|
191
|
+
ON sandbox_workspace_mutation_admissions (lease_id, workspace_generation);
|
|
192
|
+
CREATE UNIQUE INDEX sandbox_workspace_mutation_admissions_scoped_id_uq
|
|
193
|
+
ON sandbox_workspace_mutation_admissions
|
|
194
|
+
(account_id, workspace_id, session_id, lease_id, id);
|
|
195
|
+
CREATE INDEX sandbox_workspace_mutation_admissions_blocking_idx
|
|
196
|
+
ON sandbox_workspace_mutation_admissions (lease_id, workspace_generation)
|
|
197
|
+
WHERE settled_at IS NULL;
|
|
198
|
+
CREATE INDEX sandbox_workspace_mutation_admissions_attempt_idx
|
|
199
|
+
ON sandbox_workspace_mutation_admissions (workspace_id, attempt_id);
|
|
200
|
+
CREATE INDEX sandbox_workspace_mutation_admissions_actor_idx
|
|
201
|
+
ON sandbox_workspace_mutation_admissions (workspace_id, actor_kind, actor_id);
|
|
202
|
+
|
|
203
|
+
CREATE TABLE sandbox_retained_processes (
|
|
204
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
205
|
+
account_id uuid NOT NULL,
|
|
206
|
+
workspace_id uuid NOT NULL,
|
|
207
|
+
session_id uuid NOT NULL,
|
|
208
|
+
lease_id uuid NOT NULL,
|
|
209
|
+
sandbox_group_id uuid NOT NULL,
|
|
210
|
+
parent_admission_id uuid NOT NULL,
|
|
211
|
+
holder_id text NOT NULL,
|
|
212
|
+
owner_actor_kind text NOT NULL,
|
|
213
|
+
owner_actor_id uuid NOT NULL,
|
|
214
|
+
owner_turn_id uuid,
|
|
215
|
+
owner_attempt_id uuid,
|
|
216
|
+
owner_execution_generation integer,
|
|
217
|
+
lease_epoch integer NOT NULL,
|
|
218
|
+
provider_backend text NOT NULL,
|
|
219
|
+
provider_instance_id text NOT NULL,
|
|
220
|
+
route_kind text NOT NULL,
|
|
221
|
+
route_target_id uuid,
|
|
222
|
+
route_epoch integer NOT NULL,
|
|
223
|
+
provider_session_id integer NOT NULL,
|
|
224
|
+
state text NOT NULL DEFAULT 'active',
|
|
225
|
+
exit_code integer,
|
|
226
|
+
settlement_reason text,
|
|
227
|
+
started_at timestamptz NOT NULL DEFAULT now(),
|
|
228
|
+
settled_at timestamptz,
|
|
229
|
+
|
|
230
|
+
CONSTRAINT sandbox_retained_processes_workspace_account_fk
|
|
231
|
+
FOREIGN KEY (workspace_id, account_id)
|
|
232
|
+
REFERENCES workspaces (id, account_id) ON DELETE CASCADE,
|
|
233
|
+
CONSTRAINT sandbox_retained_processes_workspace_session_fk
|
|
234
|
+
FOREIGN KEY (workspace_id, session_id)
|
|
235
|
+
REFERENCES sessions (workspace_id, id) ON DELETE RESTRICT,
|
|
236
|
+
CONSTRAINT sandbox_retained_processes_parent_admission_scope_fk
|
|
237
|
+
FOREIGN KEY (account_id, workspace_id, session_id, lease_id, parent_admission_id)
|
|
238
|
+
REFERENCES sandbox_workspace_mutation_admissions
|
|
239
|
+
(account_id, workspace_id, session_id, lease_id, id)
|
|
240
|
+
ON DELETE RESTRICT,
|
|
241
|
+
CONSTRAINT sandbox_retained_processes_identity_check
|
|
242
|
+
CHECK (
|
|
243
|
+
lease_epoch >= 0
|
|
244
|
+
AND route_epoch >= 0
|
|
245
|
+
AND provider_session_id > 0
|
|
246
|
+
AND octet_length(holder_id) BETWEEN 1 AND 256
|
|
247
|
+
AND octet_length(provider_backend) BETWEEN 1 AND 64
|
|
248
|
+
AND octet_length(provider_instance_id) BETWEEN 1 AND 512
|
|
249
|
+
AND route_kind IN ('home', 'active')
|
|
250
|
+
AND (route_kind = 'active' OR route_target_id IS NULL)
|
|
251
|
+
),
|
|
252
|
+
CONSTRAINT sandbox_retained_processes_owner_check
|
|
253
|
+
CHECK (
|
|
254
|
+
(
|
|
255
|
+
owner_actor_kind = 'turn'
|
|
256
|
+
AND owner_actor_id = owner_attempt_id
|
|
257
|
+
AND owner_turn_id IS NOT NULL
|
|
258
|
+
AND owner_attempt_id IS NOT NULL
|
|
259
|
+
AND owner_execution_generation > 0
|
|
260
|
+
) OR (
|
|
261
|
+
owner_actor_kind = 'direct'
|
|
262
|
+
AND owner_turn_id IS NULL
|
|
263
|
+
AND owner_attempt_id IS NULL
|
|
264
|
+
AND owner_execution_generation IS NULL
|
|
265
|
+
)
|
|
266
|
+
),
|
|
267
|
+
CONSTRAINT sandbox_retained_processes_settlement_check
|
|
268
|
+
CHECK (
|
|
269
|
+
(state = 'active' AND settled_at IS NULL AND exit_code IS NULL)
|
|
270
|
+
OR (state = 'exited' AND settled_at IS NOT NULL)
|
|
271
|
+
OR (state = 'lost' AND settled_at IS NOT NULL AND exit_code IS NULL)
|
|
272
|
+
),
|
|
273
|
+
CONSTRAINT sandbox_retained_processes_reason_check
|
|
274
|
+
CHECK (settlement_reason IS NULL OR octet_length(settlement_reason) BETWEEN 1 AND 512)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
CREATE UNIQUE INDEX sandbox_retained_processes_scoped_id_uq
|
|
278
|
+
ON sandbox_retained_processes (account_id, workspace_id, session_id, lease_id, id);
|
|
279
|
+
CREATE UNIQUE INDEX sandbox_retained_processes_parent_admission_uq
|
|
280
|
+
ON sandbox_retained_processes (parent_admission_id);
|
|
281
|
+
CREATE UNIQUE INDEX sandbox_retained_processes_holder_uq
|
|
282
|
+
ON sandbox_retained_processes (lease_id, holder_id);
|
|
283
|
+
CREATE UNIQUE INDEX sandbox_retained_processes_live_provider_session_uq
|
|
284
|
+
ON sandbox_retained_processes
|
|
285
|
+
(lease_id, lease_epoch, provider_instance_id, route_epoch, provider_session_id)
|
|
286
|
+
WHERE state = 'active';
|
|
287
|
+
CREATE INDEX sandbox_retained_processes_active_idx
|
|
288
|
+
ON sandbox_retained_processes (workspace_id, session_id, started_at)
|
|
289
|
+
WHERE state = 'active';
|
|
290
|
+
|
|
291
|
+
-- A retained row is the durable continuation of exactly one parent admission.
|
|
292
|
+
-- This deferred check observes the final state of atomic promotion/settlement:
|
|
293
|
+
-- active => retained parent + exact non-TTL holder; terminal => settled parent
|
|
294
|
+
-- + holder removed. It never treats a numeric provider session id as authority.
|
|
295
|
+
CREATE OR REPLACE FUNCTION opengeni_private.validate_sandbox_retained_process_v2()
|
|
296
|
+
RETURNS trigger
|
|
297
|
+
LANGUAGE plpgsql
|
|
298
|
+
AS $$
|
|
299
|
+
BEGIN
|
|
300
|
+
IF NOT EXISTS (
|
|
301
|
+
SELECT 1
|
|
302
|
+
FROM sandbox_workspace_mutation_admissions admission
|
|
303
|
+
WHERE admission.id = NEW.parent_admission_id
|
|
304
|
+
AND admission.account_id = NEW.account_id
|
|
305
|
+
AND admission.workspace_id = NEW.workspace_id
|
|
306
|
+
AND admission.session_id = NEW.session_id
|
|
307
|
+
AND admission.lease_id = NEW.lease_id
|
|
308
|
+
AND admission.sandbox_group_id = NEW.sandbox_group_id
|
|
309
|
+
AND admission.actor_kind = NEW.owner_actor_kind
|
|
310
|
+
AND admission.actor_id = NEW.owner_actor_id
|
|
311
|
+
AND admission.turn_id IS NOT DISTINCT FROM NEW.owner_turn_id
|
|
312
|
+
AND admission.attempt_id IS NOT DISTINCT FROM NEW.owner_attempt_id
|
|
313
|
+
AND admission.execution_generation IS NOT DISTINCT FROM NEW.owner_execution_generation
|
|
314
|
+
AND admission.lease_epoch = NEW.lease_epoch
|
|
315
|
+
AND admission.provider_backend = NEW.provider_backend
|
|
316
|
+
AND admission.provider_instance_id = NEW.provider_instance_id
|
|
317
|
+
AND admission.route_kind = NEW.route_kind
|
|
318
|
+
AND admission.route_target_id IS NOT DISTINCT FROM NEW.route_target_id
|
|
319
|
+
AND admission.route_epoch = NEW.route_epoch
|
|
320
|
+
AND (
|
|
321
|
+
(
|
|
322
|
+
NEW.state = 'active'
|
|
323
|
+
AND admission.provider_outcome = 'retained'
|
|
324
|
+
AND admission.settled_at IS NULL
|
|
325
|
+
AND EXISTS (
|
|
326
|
+
SELECT 1 FROM sandbox_lease_holders holder
|
|
327
|
+
WHERE holder.lease_id = NEW.lease_id
|
|
328
|
+
AND holder.account_id = NEW.account_id
|
|
329
|
+
AND holder.workspace_id = NEW.workspace_id
|
|
330
|
+
AND holder.kind = 'process'
|
|
331
|
+
AND holder.holder_id = NEW.holder_id
|
|
332
|
+
AND holder.subject_id = NEW.session_id
|
|
333
|
+
)
|
|
334
|
+
) OR (
|
|
335
|
+
NEW.state IN ('exited', 'lost')
|
|
336
|
+
AND admission.provider_outcome IN ('resolved', 'rejected')
|
|
337
|
+
AND admission.settled_at IS NOT NULL
|
|
338
|
+
AND NOT EXISTS (
|
|
339
|
+
SELECT 1 FROM sandbox_lease_holders holder
|
|
340
|
+
WHERE holder.lease_id = NEW.lease_id
|
|
341
|
+
AND holder.kind = 'process'
|
|
342
|
+
AND holder.holder_id = NEW.holder_id
|
|
343
|
+
)
|
|
344
|
+
AND NOT EXISTS (
|
|
345
|
+
SELECT 1 FROM sandbox_pty_sessions pty
|
|
346
|
+
WHERE pty.retained_process_id = NEW.id
|
|
347
|
+
AND pty.status = 'open'
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
)
|
|
351
|
+
) THEN
|
|
352
|
+
RAISE EXCEPTION 'retained process does not match its parent admission and holder state'
|
|
353
|
+
USING ERRCODE = '55000';
|
|
354
|
+
END IF;
|
|
355
|
+
RETURN NEW;
|
|
356
|
+
END;
|
|
357
|
+
$$;
|
|
358
|
+
|
|
359
|
+
CREATE CONSTRAINT TRIGGER sandbox_retained_processes_identity_v2
|
|
360
|
+
AFTER INSERT OR UPDATE ON sandbox_retained_processes
|
|
361
|
+
DEFERRABLE INITIALLY DEFERRED
|
|
362
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.validate_sandbox_retained_process_v2();
|
|
363
|
+
|
|
364
|
+
-- Numeric-only PTYs cannot be made authoritative retroactively. Maintenance
|
|
365
|
+
-- closes them before the new open-row constraint is installed; callers will
|
|
366
|
+
-- observe terminal metadata rather than risking stdin against a reused id.
|
|
367
|
+
UPDATE sandbox_pty_sessions
|
|
368
|
+
SET status = 'closed', closed_at = coalesce(closed_at, clock_timestamp())
|
|
369
|
+
WHERE status = 'open';
|
|
370
|
+
|
|
371
|
+
ALTER TABLE sandbox_pty_sessions
|
|
372
|
+
ADD COLUMN IF NOT EXISTS lease_id uuid,
|
|
373
|
+
ADD COLUMN IF NOT EXISTS sandbox_group_id uuid,
|
|
374
|
+
ADD COLUMN IF NOT EXISTS retained_process_id uuid,
|
|
375
|
+
ADD COLUMN IF NOT EXISTS open_admission_id uuid,
|
|
376
|
+
ADD COLUMN IF NOT EXISTS provider_backend text,
|
|
377
|
+
ADD COLUMN IF NOT EXISTS provider_instance_id text,
|
|
378
|
+
ADD COLUMN IF NOT EXISTS route_kind text,
|
|
379
|
+
ADD COLUMN IF NOT EXISTS route_target_id uuid,
|
|
380
|
+
ADD COLUMN IF NOT EXISTS route_epoch integer;
|
|
381
|
+
|
|
382
|
+
ALTER TABLE sandbox_pty_sessions
|
|
383
|
+
DROP CONSTRAINT IF EXISTS sandbox_pty_sessions_open_identity_check;
|
|
384
|
+
ALTER TABLE sandbox_pty_sessions
|
|
385
|
+
ADD CONSTRAINT sandbox_pty_sessions_open_identity_check
|
|
386
|
+
CHECK (
|
|
387
|
+
status <> 'open' OR (
|
|
388
|
+
lease_id IS NOT NULL
|
|
389
|
+
AND sandbox_group_id IS NOT NULL
|
|
390
|
+
AND retained_process_id IS NOT NULL
|
|
391
|
+
AND open_admission_id IS NOT NULL
|
|
392
|
+
AND exec_session_id > 0
|
|
393
|
+
AND lease_epoch >= 0
|
|
394
|
+
AND octet_length(provider_backend) BETWEEN 1 AND 64
|
|
395
|
+
AND octet_length(provider_instance_id) BETWEEN 1 AND 512
|
|
396
|
+
AND route_kind IN ('home', 'active')
|
|
397
|
+
AND (route_kind = 'active' OR route_target_id IS NULL)
|
|
398
|
+
AND route_epoch >= 0
|
|
399
|
+
)
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
ALTER TABLE sandbox_pty_sessions
|
|
403
|
+
ADD CONSTRAINT sandbox_pty_sessions_retained_process_scope_fk
|
|
404
|
+
FOREIGN KEY (account_id, workspace_id, session_id, lease_id, retained_process_id)
|
|
405
|
+
REFERENCES sandbox_retained_processes
|
|
406
|
+
(account_id, workspace_id, session_id, lease_id, id)
|
|
407
|
+
ON DELETE RESTRICT;
|
|
408
|
+
|
|
409
|
+
CREATE UNIQUE INDEX sandbox_pty_sessions_open_process_uq
|
|
410
|
+
ON sandbox_pty_sessions (retained_process_id)
|
|
411
|
+
WHERE status = 'open';
|
|
412
|
+
|
|
413
|
+
-- Cross-table semantics that cannot be expressed as a CHECK: an open PTY must
|
|
414
|
+
-- copy the exact active retained process, including its parent admission and
|
|
415
|
+
-- pinned provider/route identity. Closed legacy rows remain readable evidence.
|
|
416
|
+
CREATE OR REPLACE FUNCTION opengeni_private.validate_sandbox_pty_process_v2()
|
|
417
|
+
RETURNS trigger
|
|
418
|
+
LANGUAGE plpgsql
|
|
419
|
+
AS $$
|
|
420
|
+
BEGIN
|
|
421
|
+
IF NEW.status = 'open' AND NOT EXISTS (
|
|
422
|
+
SELECT 1
|
|
423
|
+
FROM sandbox_retained_processes process
|
|
424
|
+
WHERE process.id = NEW.retained_process_id
|
|
425
|
+
AND process.account_id = NEW.account_id
|
|
426
|
+
AND process.workspace_id = NEW.workspace_id
|
|
427
|
+
AND process.session_id = NEW.session_id
|
|
428
|
+
AND process.lease_id = NEW.lease_id
|
|
429
|
+
AND process.sandbox_group_id = NEW.sandbox_group_id
|
|
430
|
+
AND process.parent_admission_id = NEW.open_admission_id
|
|
431
|
+
AND process.lease_epoch = NEW.lease_epoch
|
|
432
|
+
AND process.provider_backend = NEW.provider_backend
|
|
433
|
+
AND process.provider_instance_id = NEW.provider_instance_id
|
|
434
|
+
AND process.route_kind = NEW.route_kind
|
|
435
|
+
AND process.route_target_id IS NOT DISTINCT FROM NEW.route_target_id
|
|
436
|
+
AND process.route_epoch = NEW.route_epoch
|
|
437
|
+
AND process.provider_session_id = NEW.exec_session_id
|
|
438
|
+
AND process.state = 'active'
|
|
439
|
+
) THEN
|
|
440
|
+
RAISE EXCEPTION 'open PTY does not match its exact retained process identity'
|
|
441
|
+
USING ERRCODE = '55000';
|
|
442
|
+
END IF;
|
|
443
|
+
RETURN NEW;
|
|
444
|
+
END;
|
|
445
|
+
$$;
|
|
446
|
+
|
|
447
|
+
CREATE CONSTRAINT TRIGGER sandbox_pty_sessions_process_identity_v2
|
|
448
|
+
AFTER INSERT OR UPDATE ON sandbox_pty_sessions
|
|
449
|
+
DEFERRABLE INITIALLY DEFERRED
|
|
450
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.validate_sandbox_pty_process_v2();
|
|
451
|
+
|
|
452
|
+
-- Preserve archive truth when 0108's warming owner expires. The helper strips
|
|
453
|
+
-- live provider/rematerialization identity and publishes pending only when the
|
|
454
|
+
-- archived generation exactly covers the closed workspace generation.
|
|
455
|
+
CREATE OR REPLACE FUNCTION opengeni_private.warming_reset_resume_state_v2(
|
|
456
|
+
p_backend text,
|
|
457
|
+
p_resume_backend_id text,
|
|
458
|
+
p_resume_state jsonb,
|
|
459
|
+
p_workspace_generation integer,
|
|
460
|
+
p_archive_generation integer,
|
|
461
|
+
p_reset_at timestamptz
|
|
462
|
+
)
|
|
463
|
+
RETURNS jsonb
|
|
464
|
+
LANGUAGE sql
|
|
465
|
+
AS $$
|
|
466
|
+
SELECT CASE
|
|
467
|
+
WHEN coalesce(p_resume_state #>> '{sessionState,workspaceArchive}', '') <> ''
|
|
468
|
+
OR coalesce(p_resume_state #>> '{sessionState,workspaceArchivePrev}', '') <> ''
|
|
469
|
+
THEN jsonb_strip_nulls(jsonb_build_object(
|
|
470
|
+
'backendId', coalesce(p_resume_state ->> 'backendId', p_resume_backend_id, p_backend),
|
|
471
|
+
'sessionState', jsonb_strip_nulls(jsonb_build_object(
|
|
472
|
+
'workspaceArchive', p_resume_state #> '{sessionState,workspaceArchive}',
|
|
473
|
+
'workspaceArchiveMeta', p_resume_state #> '{sessionState,workspaceArchiveMeta}',
|
|
474
|
+
'workspaceArchivePrev', p_resume_state #> '{sessionState,workspaceArchivePrev}',
|
|
475
|
+
'workspaceArchivePrevMeta', p_resume_state #> '{sessionState,workspaceArchivePrevMeta}',
|
|
476
|
+
'workspaceArchiveAt', p_resume_state #> '{sessionState,workspaceArchiveAt}'
|
|
477
|
+
)),
|
|
478
|
+
'opengeniRecovery', jsonb_build_object(
|
|
479
|
+
'provider', jsonb_build_object(
|
|
480
|
+
'status', 'not_created', 'instanceId', null, 'observedAt', to_jsonb(p_reset_at)
|
|
481
|
+
),
|
|
482
|
+
'archive', coalesce(
|
|
483
|
+
p_resume_state #> '{opengeniRecovery,archive}',
|
|
484
|
+
jsonb_build_object(
|
|
485
|
+
'status', CASE
|
|
486
|
+
WHEN coalesce(p_resume_state #>> '{sessionState,workspaceArchive}', '') <> ''
|
|
487
|
+
AND p_resume_state #> '{sessionState,workspaceArchiveMeta}' IS NOT NULL
|
|
488
|
+
THEN 'available' ELSE 'unverified' END,
|
|
489
|
+
'current', p_resume_state #> '{sessionState,workspaceArchiveMeta}',
|
|
490
|
+
'previous', p_resume_state #> '{sessionState,workspaceArchivePrevMeta}'
|
|
491
|
+
)
|
|
492
|
+
),
|
|
493
|
+
'restore', jsonb_strip_nulls(jsonb_build_object(
|
|
494
|
+
'status', CASE
|
|
495
|
+
WHEN p_archive_generation = p_workspace_generation
|
|
496
|
+
AND coalesce(p_resume_state #>> '{sessionState,workspaceArchive}', '') <> ''
|
|
497
|
+
AND p_resume_state #> '{sessionState,workspaceArchiveMeta}' IS NOT NULL
|
|
498
|
+
THEN 'pending' ELSE 'degraded' END,
|
|
499
|
+
'rematerializationId', null,
|
|
500
|
+
'selectedRevision', coalesce(
|
|
501
|
+
p_resume_state #>> '{opengeniRecovery,archive,current,revision}',
|
|
502
|
+
p_resume_state #>> '{sessionState,workspaceArchiveMeta,revision}'
|
|
503
|
+
),
|
|
504
|
+
'startedAt', null,
|
|
505
|
+
'completedAt', to_jsonb(p_reset_at),
|
|
506
|
+
'failureCode', CASE
|
|
507
|
+
WHEN p_archive_generation = p_workspace_generation
|
|
508
|
+
AND coalesce(p_resume_state #>> '{sessionState,workspaceArchive}', '') <> ''
|
|
509
|
+
AND p_resume_state #> '{sessionState,workspaceArchiveMeta}' IS NOT NULL
|
|
510
|
+
THEN null ELSE 'archive_generation_mismatch' END,
|
|
511
|
+
'retryable', CASE
|
|
512
|
+
WHEN p_archive_generation = p_workspace_generation
|
|
513
|
+
AND coalesce(p_resume_state #>> '{sessionState,workspaceArchive}', '') <> ''
|
|
514
|
+
AND p_resume_state #> '{sessionState,workspaceArchiveMeta}' IS NOT NULL
|
|
515
|
+
THEN null ELSE to_jsonb(false) END
|
|
516
|
+
)),
|
|
517
|
+
'workspace', jsonb_build_object(
|
|
518
|
+
'status', CASE
|
|
519
|
+
WHEN p_archive_generation = p_workspace_generation
|
|
520
|
+
AND coalesce(p_resume_state #>> '{sessionState,workspaceArchive}', '') <> ''
|
|
521
|
+
AND p_resume_state #> '{sessionState,workspaceArchiveMeta}' IS NOT NULL
|
|
522
|
+
THEN 'not_ready' ELSE 'degraded' END,
|
|
523
|
+
'verifiedRevision', null, 'verifiedAt', null
|
|
524
|
+
)
|
|
525
|
+
)
|
|
526
|
+
))
|
|
527
|
+
ELSE null
|
|
528
|
+
END;
|
|
529
|
+
$$;
|
|
530
|
+
|
|
531
|
+
-- Replace 0108 in place. Direct holders share the bounded request TTL with
|
|
532
|
+
-- viewers. Process holders are deliberately absent from every TTL delete and
|
|
533
|
+
-- can leave only through exact retained-process settlement.
|
|
534
|
+
CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
|
|
535
|
+
p_viewer_holder_ttl_ms bigint,
|
|
536
|
+
p_turn_holder_ttl_ms bigint,
|
|
537
|
+
p_idle_grace_ms bigint
|
|
538
|
+
)
|
|
539
|
+
RETURNS TABLE (workspace_id uuid, sandbox_group_id uuid, instance_id text, lease_epoch integer)
|
|
540
|
+
LANGUAGE plpgsql
|
|
541
|
+
SECURITY DEFINER
|
|
542
|
+
AS $$
|
|
543
|
+
BEGIN
|
|
544
|
+
PERFORM set_config('opengeni.sandbox_recovery_protocol_v2', '1', true);
|
|
545
|
+
|
|
546
|
+
DELETE FROM sandbox_lease_holders holder
|
|
547
|
+
WHERE holder.kind IN ('viewer', 'direct')
|
|
548
|
+
AND holder.last_heartbeat_at
|
|
549
|
+
< now() - make_interval(secs => p_viewer_holder_ttl_ms / 1000.0);
|
|
550
|
+
|
|
551
|
+
IF p_turn_holder_ttl_ms > 0 THEN
|
|
552
|
+
DELETE FROM sandbox_lease_holders holder
|
|
553
|
+
WHERE holder.kind = 'turn'
|
|
554
|
+
AND holder.last_heartbeat_at
|
|
555
|
+
< now() - make_interval(secs => p_turn_holder_ttl_ms / 1000.0);
|
|
556
|
+
END IF;
|
|
557
|
+
|
|
558
|
+
UPDATE sandbox_leases lease SET
|
|
559
|
+
refcount = counts.total,
|
|
560
|
+
turn_holders = counts.turns,
|
|
561
|
+
viewer_holders = counts.viewers,
|
|
562
|
+
liveness = CASE
|
|
563
|
+
WHEN lease.liveness = 'warm' AND counts.total = 0 AND counts.turns = 0
|
|
564
|
+
THEN 'draining' ELSE lease.liveness END,
|
|
565
|
+
expires_at = CASE
|
|
566
|
+
WHEN lease.liveness = 'warm' AND counts.total = 0 AND counts.turns = 0
|
|
567
|
+
THEN now() + make_interval(secs => p_idle_grace_ms / 1000.0)
|
|
568
|
+
ELSE lease.expires_at END,
|
|
569
|
+
updated_at = now()
|
|
570
|
+
FROM (
|
|
571
|
+
SELECT candidate.id,
|
|
572
|
+
(SELECT count(*) FROM sandbox_lease_holders holder
|
|
573
|
+
WHERE holder.lease_id = candidate.id)::int AS total,
|
|
574
|
+
(SELECT count(*) FROM sandbox_lease_holders holder
|
|
575
|
+
WHERE holder.lease_id = candidate.id AND holder.kind = 'turn')::int AS turns,
|
|
576
|
+
(SELECT count(*) FROM sandbox_lease_holders holder
|
|
577
|
+
WHERE holder.lease_id = candidate.id AND holder.kind = 'viewer')::int AS viewers
|
|
578
|
+
FROM sandbox_leases candidate
|
|
579
|
+
) counts
|
|
580
|
+
WHERE lease.id = counts.id;
|
|
581
|
+
|
|
582
|
+
UPDATE sandbox_leases lease SET
|
|
583
|
+
liveness = 'cold',
|
|
584
|
+
instance_id = null,
|
|
585
|
+
lease_epoch = lease.lease_epoch + 1,
|
|
586
|
+
resume_state = opengeni_private.warming_reset_resume_state_v2(
|
|
587
|
+
lease.backend, lease.resume_backend_id, lease.resume_state,
|
|
588
|
+
lease.workspace_generation, lease.archive_generation, clock_timestamp()
|
|
589
|
+
),
|
|
590
|
+
resume_backend_id = CASE
|
|
591
|
+
WHEN coalesce(lease.resume_state #>> '{sessionState,workspaceArchive}', '') <> ''
|
|
592
|
+
OR coalesce(lease.resume_state #>> '{sessionState,workspaceArchivePrev}', '') <> ''
|
|
593
|
+
THEN coalesce(lease.resume_backend_id, lease.backend)
|
|
594
|
+
ELSE null END,
|
|
595
|
+
data_plane_url = null,
|
|
596
|
+
terminal_data_plane_url = null,
|
|
597
|
+
updated_at = now()
|
|
598
|
+
WHERE lease.liveness = 'warming'
|
|
599
|
+
AND lease.expires_at < now()
|
|
600
|
+
AND lease.instance_id IS NULL;
|
|
601
|
+
|
|
602
|
+
UPDATE sandbox_leases lease SET
|
|
603
|
+
liveness = 'draining',
|
|
604
|
+
refcount = 0,
|
|
605
|
+
turn_holders = 0,
|
|
606
|
+
viewer_holders = 0,
|
|
607
|
+
data_plane_url = null,
|
|
608
|
+
terminal_data_plane_url = null,
|
|
609
|
+
lease_epoch = lease.lease_epoch + 1,
|
|
610
|
+
expires_at = now() - interval '1 millisecond',
|
|
611
|
+
updated_at = now()
|
|
612
|
+
WHERE lease.liveness = 'warming'
|
|
613
|
+
AND lease.expires_at < now()
|
|
614
|
+
AND lease.instance_id IS NOT NULL;
|
|
615
|
+
|
|
616
|
+
RETURN QUERY
|
|
617
|
+
SELECT lease.workspace_id, lease.sandbox_group_id,
|
|
618
|
+
lease.instance_id, lease.lease_epoch
|
|
619
|
+
FROM sandbox_leases lease
|
|
620
|
+
WHERE lease.liveness = 'draining'
|
|
621
|
+
AND lease.expires_at < now()
|
|
622
|
+
AND lease.refcount = 0;
|
|
623
|
+
END;
|
|
624
|
+
$$;
|
|
625
|
+
|
|
626
|
+
-- No mixed old/new writer interval: after maintenance activation every lease,
|
|
627
|
+
-- holder, admission, retained-process, and PTY mutation by a non-superuser must
|
|
628
|
+
-- explicitly opt into v2 in its current transaction.
|
|
629
|
+
CREATE OR REPLACE FUNCTION opengeni_private.enforce_sandbox_recovery_protocol_v2()
|
|
630
|
+
RETURNS trigger
|
|
631
|
+
LANGUAGE plpgsql
|
|
632
|
+
AS $$
|
|
633
|
+
BEGIN
|
|
634
|
+
IF NOT EXISTS (
|
|
635
|
+
SELECT 1 FROM pg_roles WHERE rolname = session_user AND rolsuper
|
|
636
|
+
) AND current_setting('opengeni.sandbox_recovery_protocol_v2', true) IS DISTINCT FROM '1' THEN
|
|
637
|
+
RAISE EXCEPTION 'sandbox recovery protocol v2 marker is required for % on %', TG_OP, TG_TABLE_NAME
|
|
638
|
+
USING ERRCODE = '55000';
|
|
639
|
+
END IF;
|
|
640
|
+
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
|
641
|
+
RETURN NEW;
|
|
642
|
+
END;
|
|
643
|
+
$$;
|
|
644
|
+
|
|
645
|
+
CREATE TRIGGER sandbox_recovery_protocol_v2_lease_guard
|
|
646
|
+
BEFORE INSERT OR UPDATE OR DELETE ON sandbox_leases
|
|
647
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.enforce_sandbox_recovery_protocol_v2();
|
|
648
|
+
CREATE TRIGGER sandbox_recovery_protocol_v2_holder_guard
|
|
649
|
+
BEFORE INSERT OR UPDATE OR DELETE ON sandbox_lease_holders
|
|
650
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.enforce_sandbox_recovery_protocol_v2();
|
|
651
|
+
CREATE TRIGGER sandbox_recovery_protocol_v2_admission_guard
|
|
652
|
+
BEFORE INSERT OR UPDATE OR DELETE ON sandbox_workspace_mutation_admissions
|
|
653
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.enforce_sandbox_recovery_protocol_v2();
|
|
654
|
+
CREATE TRIGGER sandbox_recovery_protocol_v2_process_guard
|
|
655
|
+
BEFORE INSERT OR UPDATE OR DELETE ON sandbox_retained_processes
|
|
656
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.enforce_sandbox_recovery_protocol_v2();
|
|
657
|
+
CREATE TRIGGER sandbox_recovery_protocol_v2_pty_guard
|
|
658
|
+
BEFORE INSERT OR UPDATE OR DELETE ON sandbox_pty_sessions
|
|
659
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.enforce_sandbox_recovery_protocol_v2();
|
|
660
|
+
|
|
661
|
+
ALTER TABLE sandbox_workspace_mutation_admissions ENABLE ROW LEVEL SECURITY;
|
|
662
|
+
ALTER TABLE sandbox_workspace_mutation_admissions FORCE ROW LEVEL SECURITY;
|
|
663
|
+
ALTER TABLE sandbox_retained_processes ENABLE ROW LEVEL SECURITY;
|
|
664
|
+
ALTER TABLE sandbox_retained_processes FORCE ROW LEVEL SECURITY;
|
|
665
|
+
|
|
666
|
+
CREATE POLICY workspace_isolation ON sandbox_workspace_mutation_admissions
|
|
667
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
668
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
669
|
+
CREATE POLICY workspace_isolation ON sandbox_retained_processes
|
|
670
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
671
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
672
|
+
|
|
673
|
+
DO $grants$
|
|
674
|
+
DECLARE data_schema text := current_schema();
|
|
675
|
+
BEGIN
|
|
676
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
677
|
+
EXECUTE format(
|
|
678
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.sandbox_workspace_mutation_admissions TO opengeni_app',
|
|
679
|
+
data_schema
|
|
680
|
+
);
|
|
681
|
+
EXECUTE format(
|
|
682
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.sandbox_retained_processes TO opengeni_app',
|
|
683
|
+
data_schema
|
|
684
|
+
);
|
|
685
|
+
EXECUTE format(
|
|
686
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.sandbox_leases, %I.sandbox_lease_holders, %I.sandbox_pty_sessions TO opengeni_app',
|
|
687
|
+
data_schema, data_schema, data_schema
|
|
688
|
+
);
|
|
689
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.reap_sandbox_leases(bigint, bigint, bigint)
|
|
690
|
+
TO opengeni_app;
|
|
691
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.enforce_sandbox_recovery_protocol_v2()
|
|
692
|
+
TO opengeni_app;
|
|
693
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.validate_sandbox_pty_process_v2()
|
|
694
|
+
TO opengeni_app;
|
|
695
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.validate_sandbox_retained_process_v2()
|
|
696
|
+
TO opengeni_app;
|
|
697
|
+
END IF;
|
|
698
|
+
END
|
|
699
|
+
$grants$;
|