@opengeni/db 0.12.1 → 0.13.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-ELMUCYZF.js +906 -0
- package/dist/chunk-ELMUCYZF.js.map +1 -0
- package/dist/chunk-N4WTE6SB.js +4033 -0
- package/dist/chunk-N4WTE6SB.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3594 -2059
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +472 -43
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-BejThLcd.d.ts → schema-Bh1Hr7xY.d.ts} +2507 -970
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +9 -1
- package/drizzle/0122_codex_capacity_same_turn.sql +59 -0
- package/drizzle/0123_session_tool_policy_version.sql +14 -0
- package/drizzle/0124_session_event_duplicate_lookup.sql +4 -0
- package/drizzle/0125_document_drops_visibility.sql +29 -0
- package/drizzle/0126_document_access_constraints.sql +138 -0
- package/drizzle/0127_document_default_base_index.sql +5 -0
- package/drizzle/0128_github_installation_authority.sql +69 -0
- package/drizzle/0129_retained_process_reconciliation.sql +356 -0
- package/drizzle/0130_workspace_instruction_policies.sql +330 -0
- package/drizzle/0131_slack_bot_install_and_post_idempotency.sql +133 -0
- package/package.json +4 -3
- package/src/event-payload-sanitizer.ts +167 -55
- package/src/index.ts +1850 -264
- package/src/new-session-drafts.ts +127 -6
- package/src/provision-roles.ts +184 -2
- package/src/runtime-posture-cli.ts +57 -0
- package/src/runtime-posture.ts +770 -0
- package/src/schema.ts +243 -3
- package/src/session-control.ts +1 -0
- package/src/workspace-instruction-policies-schema.ts +140 -0
- package/src/workspace-instruction-policies.ts +624 -0
- package/dist/chunk-BMFDXFPA.js +0 -155
- package/dist/chunk-BMFDXFPA.js.map +0 -1
- package/dist/chunk-VUKRIBO5.js +0 -3679
- package/dist/chunk-VUKRIBO5.js.map +0 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Terminal owners do not prove provider quiescence. Persist only a
|
|
3
|
+
-- bounded/fair reconciliation claim here; the worker must still obtain an exact
|
|
4
|
+
-- provider exit/loss proof before invoking canonical retained-process settlement.
|
|
5
|
+
|
|
6
|
+
ALTER TABLE "sandbox_retained_processes"
|
|
7
|
+
ADD COLUMN IF NOT EXISTS "reconcile_after" timestamptz NOT NULL DEFAULT now(),
|
|
8
|
+
ADD COLUMN IF NOT EXISTS "reconcile_claim_id" uuid,
|
|
9
|
+
ADD COLUMN IF NOT EXISTS "reconcile_claimed_at" timestamptz,
|
|
10
|
+
ADD COLUMN IF NOT EXISTS "reconcile_attempts" integer NOT NULL DEFAULT 0,
|
|
11
|
+
ADD COLUMN IF NOT EXISTS "last_reconcile_outcome" text,
|
|
12
|
+
ADD COLUMN IF NOT EXISTS "reconcile_proof_outcome" text,
|
|
13
|
+
ADD COLUMN IF NOT EXISTS "reconcile_proof_exit_code" integer,
|
|
14
|
+
ADD COLUMN IF NOT EXISTS "reconcile_proof_reason" text,
|
|
15
|
+
ADD COLUMN IF NOT EXISTS "reconcile_proof_observed_at" timestamptz;
|
|
16
|
+
|
|
17
|
+
ALTER TABLE "sandbox_retained_processes"
|
|
18
|
+
ADD CONSTRAINT "sandbox_retained_processes_reconcile_claim_check"
|
|
19
|
+
CHECK (
|
|
20
|
+
("reconcile_claim_id" IS NULL AND "reconcile_claimed_at" IS NULL)
|
|
21
|
+
OR ("reconcile_claim_id" IS NOT NULL AND "reconcile_claimed_at" IS NOT NULL)
|
|
22
|
+
),
|
|
23
|
+
ADD CONSTRAINT "sandbox_retained_processes_reconcile_attempts_check"
|
|
24
|
+
CHECK ("reconcile_attempts" >= 0),
|
|
25
|
+
ADD CONSTRAINT "sandbox_retained_processes_reconcile_outcome_check"
|
|
26
|
+
CHECK (
|
|
27
|
+
"last_reconcile_outcome" IS NULL
|
|
28
|
+
OR octet_length("last_reconcile_outcome") BETWEEN 1 AND 64
|
|
29
|
+
),
|
|
30
|
+
ADD CONSTRAINT "sandbox_retained_processes_reconcile_proof_check"
|
|
31
|
+
CHECK (
|
|
32
|
+
(
|
|
33
|
+
"reconcile_proof_outcome" IS NULL
|
|
34
|
+
AND "reconcile_proof_exit_code" IS NULL
|
|
35
|
+
AND "reconcile_proof_reason" IS NULL
|
|
36
|
+
AND "reconcile_proof_observed_at" IS NULL
|
|
37
|
+
) OR (
|
|
38
|
+
"reconcile_proof_outcome" = 'exited'
|
|
39
|
+
AND "reconcile_proof_exit_code" IS NOT NULL
|
|
40
|
+
AND "reconcile_proof_reason" = 'provider_exit_banner'
|
|
41
|
+
AND "reconcile_proof_observed_at" IS NOT NULL
|
|
42
|
+
) OR (
|
|
43
|
+
"reconcile_proof_outcome" = 'lost'
|
|
44
|
+
AND "reconcile_proof_exit_code" IS NULL
|
|
45
|
+
AND "reconcile_proof_reason" IN (
|
|
46
|
+
'provider_session_lost_banner', 'provider_instance_not_found'
|
|
47
|
+
)
|
|
48
|
+
AND "reconcile_proof_observed_at" IS NOT NULL
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE INDEX "sandbox_retained_processes_reconcile_due_idx"
|
|
53
|
+
ON "sandbox_retained_processes" ("reconcile_after", "started_at", "id")
|
|
54
|
+
WHERE "state" = 'active';
|
|
55
|
+
|
|
56
|
+
CREATE INDEX "sandbox_retained_processes_active_inventory_idx"
|
|
57
|
+
ON "sandbox_retained_processes" (
|
|
58
|
+
"owner_actor_kind", "workspace_id", "owner_turn_id", "owner_attempt_id"
|
|
59
|
+
)
|
|
60
|
+
WHERE "state" = 'active';
|
|
61
|
+
|
|
62
|
+
CREATE INDEX "sandbox_leases_expired_draining_inventory_idx"
|
|
63
|
+
ON "sandbox_leases" ("expires_at", "backend")
|
|
64
|
+
WHERE "liveness" = 'draining';
|
|
65
|
+
|
|
66
|
+
-- The data schema is selected by the migration caller and may be public or a
|
|
67
|
+
-- dedicated embed schema. Bind it into every privileged statement at creation
|
|
68
|
+
-- time, then expose only pg_catalog at execution time. pg_temp and the caller's
|
|
69
|
+
-- search_path can therefore resolve neither relations nor helper functions.
|
|
70
|
+
DO $privileged_functions$
|
|
71
|
+
DECLARE data_schema text := current_schema();
|
|
72
|
+
BEGIN
|
|
73
|
+
-- The claim update fires this pre-existing deferred trigger while the claim
|
|
74
|
+
-- function's pg_catalog-only path is active. Replace its 0117 body in place so
|
|
75
|
+
-- the transitive identity check is equally independent of caller/pg_temp path.
|
|
76
|
+
EXECUTE format($create$
|
|
77
|
+
CREATE OR REPLACE FUNCTION opengeni_private.validate_sandbox_retained_process_v2()
|
|
78
|
+
RETURNS trigger
|
|
79
|
+
LANGUAGE plpgsql
|
|
80
|
+
SET search_path = pg_catalog
|
|
81
|
+
AS $function$
|
|
82
|
+
BEGIN
|
|
83
|
+
IF NOT EXISTS (
|
|
84
|
+
SELECT 1
|
|
85
|
+
FROM %1$I.sandbox_workspace_mutation_admissions admission
|
|
86
|
+
WHERE admission.id = NEW.parent_admission_id
|
|
87
|
+
AND admission.account_id = NEW.account_id
|
|
88
|
+
AND admission.workspace_id = NEW.workspace_id
|
|
89
|
+
AND admission.session_id = NEW.session_id
|
|
90
|
+
AND admission.lease_id = NEW.lease_id
|
|
91
|
+
AND admission.sandbox_group_id = NEW.sandbox_group_id
|
|
92
|
+
AND admission.actor_kind = NEW.owner_actor_kind
|
|
93
|
+
AND admission.actor_id = NEW.owner_actor_id
|
|
94
|
+
AND admission.turn_id IS NOT DISTINCT FROM NEW.owner_turn_id
|
|
95
|
+
AND admission.attempt_id IS NOT DISTINCT FROM NEW.owner_attempt_id
|
|
96
|
+
AND admission.execution_generation IS NOT DISTINCT FROM NEW.owner_execution_generation
|
|
97
|
+
AND admission.lease_epoch = NEW.lease_epoch
|
|
98
|
+
AND admission.provider_backend = NEW.provider_backend
|
|
99
|
+
AND admission.provider_instance_id = NEW.provider_instance_id
|
|
100
|
+
AND admission.route_kind = NEW.route_kind
|
|
101
|
+
AND admission.route_target_id IS NOT DISTINCT FROM NEW.route_target_id
|
|
102
|
+
AND admission.route_epoch = NEW.route_epoch
|
|
103
|
+
AND (
|
|
104
|
+
(
|
|
105
|
+
NEW.state = 'active'
|
|
106
|
+
AND admission.provider_outcome = 'retained'
|
|
107
|
+
AND admission.settled_at IS NULL
|
|
108
|
+
AND EXISTS (
|
|
109
|
+
SELECT 1 FROM %1$I.sandbox_lease_holders holder
|
|
110
|
+
WHERE holder.lease_id = NEW.lease_id
|
|
111
|
+
AND holder.account_id = NEW.account_id
|
|
112
|
+
AND holder.workspace_id = NEW.workspace_id
|
|
113
|
+
AND holder.kind = 'process'
|
|
114
|
+
AND holder.holder_id = NEW.holder_id
|
|
115
|
+
AND holder.subject_id = NEW.session_id
|
|
116
|
+
)
|
|
117
|
+
) OR (
|
|
118
|
+
NEW.state IN ('exited', 'lost')
|
|
119
|
+
AND admission.provider_outcome IN ('resolved', 'rejected')
|
|
120
|
+
AND admission.settled_at IS NOT NULL
|
|
121
|
+
AND NOT EXISTS (
|
|
122
|
+
SELECT 1 FROM %1$I.sandbox_lease_holders holder
|
|
123
|
+
WHERE holder.lease_id = NEW.lease_id
|
|
124
|
+
AND holder.kind = 'process'
|
|
125
|
+
AND holder.holder_id = NEW.holder_id
|
|
126
|
+
)
|
|
127
|
+
AND NOT EXISTS (
|
|
128
|
+
SELECT 1 FROM %1$I.sandbox_pty_sessions pty
|
|
129
|
+
WHERE pty.retained_process_id = NEW.id
|
|
130
|
+
AND pty.status = 'open'
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
) THEN
|
|
135
|
+
RAISE EXCEPTION 'retained process does not match its parent admission and holder state'
|
|
136
|
+
USING ERRCODE = '55000';
|
|
137
|
+
END IF;
|
|
138
|
+
RETURN NEW;
|
|
139
|
+
END;
|
|
140
|
+
$function$;
|
|
141
|
+
$create$, data_schema);
|
|
142
|
+
|
|
143
|
+
-- Cross-workspace claim for the sole control-worker reaper. The index-backed
|
|
144
|
+
-- candidate window is locked and limited before owner eligibility joins. A
|
|
145
|
+
-- live owner is deferred so it cannot permanently occupy the oldest due edge.
|
|
146
|
+
-- Claim expiry is represented by reconcile_after; it remains coordination
|
|
147
|
+
-- recovery only and is never physical-exit proof.
|
|
148
|
+
EXECUTE format($create$
|
|
149
|
+
CREATE OR REPLACE FUNCTION opengeni_private.claim_terminal_retained_processes(
|
|
150
|
+
p_claim_id uuid,
|
|
151
|
+
p_limit integer,
|
|
152
|
+
p_claim_ttl_ms bigint
|
|
153
|
+
)
|
|
154
|
+
RETURNS TABLE (
|
|
155
|
+
account_id uuid,
|
|
156
|
+
workspace_id uuid,
|
|
157
|
+
session_id uuid,
|
|
158
|
+
process_id uuid,
|
|
159
|
+
claim_id uuid,
|
|
160
|
+
owner_state text,
|
|
161
|
+
owner_attempt_outcome text
|
|
162
|
+
)
|
|
163
|
+
LANGUAGE plpgsql
|
|
164
|
+
SECURITY DEFINER
|
|
165
|
+
SET search_path = pg_catalog
|
|
166
|
+
AS $function$
|
|
167
|
+
BEGIN
|
|
168
|
+
IF p_limit < 1 OR p_limit > 100 THEN
|
|
169
|
+
RAISE EXCEPTION 'retained-process reconciliation limit must be between 1 and 100'
|
|
170
|
+
USING ERRCODE = '22023';
|
|
171
|
+
END IF;
|
|
172
|
+
IF p_claim_ttl_ms < 0 OR p_claim_ttl_ms > 3600000 THEN
|
|
173
|
+
RAISE EXCEPTION 'retained-process reconciliation claim TTL is invalid'
|
|
174
|
+
USING ERRCODE = '22023';
|
|
175
|
+
END IF;
|
|
176
|
+
|
|
177
|
+
PERFORM pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2', '1', true);
|
|
178
|
+
|
|
179
|
+
RETURN QUERY
|
|
180
|
+
WITH candidate_window AS MATERIALIZED (
|
|
181
|
+
SELECT process.id,
|
|
182
|
+
process.workspace_id,
|
|
183
|
+
process.owner_actor_kind,
|
|
184
|
+
process.owner_turn_id,
|
|
185
|
+
process.owner_attempt_id,
|
|
186
|
+
process.reconcile_after AS due_at,
|
|
187
|
+
process.started_at
|
|
188
|
+
FROM %1$I.sandbox_retained_processes process
|
|
189
|
+
WHERE process.state = 'active'
|
|
190
|
+
AND process.reconcile_after <= pg_catalog.now()
|
|
191
|
+
ORDER BY process.reconcile_after, process.started_at, process.id
|
|
192
|
+
FOR UPDATE OF process SKIP LOCKED
|
|
193
|
+
LIMIT p_limit
|
|
194
|
+
), classified AS MATERIALIZED (
|
|
195
|
+
SELECT candidate.id,
|
|
196
|
+
candidate.due_at,
|
|
197
|
+
candidate.started_at,
|
|
198
|
+
candidate.owner_actor_kind = 'direct' OR attempt.state = 'closed' AS eligible,
|
|
199
|
+
CASE
|
|
200
|
+
WHEN candidate.owner_actor_kind = 'direct' THEN 'direct'
|
|
201
|
+
ELSE COALESCE(turn_row.status, 'missing')
|
|
202
|
+
END AS owner_state,
|
|
203
|
+
attempt.outcome AS owner_attempt_outcome
|
|
204
|
+
FROM candidate_window candidate
|
|
205
|
+
LEFT JOIN LATERAL (
|
|
206
|
+
SELECT source_turn.status
|
|
207
|
+
FROM %1$I.session_turns source_turn
|
|
208
|
+
WHERE source_turn.workspace_id = candidate.workspace_id
|
|
209
|
+
AND source_turn.id = candidate.owner_turn_id
|
|
210
|
+
LIMIT 1
|
|
211
|
+
) turn_row ON true
|
|
212
|
+
LEFT JOIN LATERAL (
|
|
213
|
+
SELECT source_attempt.state, source_attempt.outcome
|
|
214
|
+
FROM %1$I.session_turn_attempts source_attempt
|
|
215
|
+
WHERE source_attempt.workspace_id = candidate.workspace_id
|
|
216
|
+
AND source_attempt.id = candidate.owner_attempt_id
|
|
217
|
+
LIMIT 1
|
|
218
|
+
) attempt ON true
|
|
219
|
+
), inspected AS (
|
|
220
|
+
UPDATE %1$I.sandbox_retained_processes process SET
|
|
221
|
+
reconcile_after = CASE
|
|
222
|
+
WHEN classified.eligible THEN pg_catalog.now()
|
|
223
|
+
+ pg_catalog.make_interval(secs => p_claim_ttl_ms / 1000.0)
|
|
224
|
+
ELSE pg_catalog.now() + interval '30 seconds'
|
|
225
|
+
END,
|
|
226
|
+
reconcile_claim_id = CASE WHEN classified.eligible THEN p_claim_id ELSE NULL END,
|
|
227
|
+
reconcile_claimed_at = CASE
|
|
228
|
+
WHEN classified.eligible THEN pg_catalog.now() ELSE NULL
|
|
229
|
+
END,
|
|
230
|
+
reconcile_attempts = process.reconcile_attempts
|
|
231
|
+
+ CASE WHEN classified.eligible THEN 1 ELSE 0 END,
|
|
232
|
+
last_reconcile_outcome = CASE
|
|
233
|
+
WHEN classified.eligible THEN 'claimed' ELSE 'owner_active'
|
|
234
|
+
END
|
|
235
|
+
FROM classified
|
|
236
|
+
WHERE process.id = classified.id
|
|
237
|
+
AND process.state = 'active'
|
|
238
|
+
RETURNING process.account_id,
|
|
239
|
+
process.workspace_id,
|
|
240
|
+
process.session_id,
|
|
241
|
+
process.id,
|
|
242
|
+
process.reconcile_claim_id,
|
|
243
|
+
classified.due_at,
|
|
244
|
+
classified.started_at,
|
|
245
|
+
classified.eligible,
|
|
246
|
+
classified.owner_state,
|
|
247
|
+
classified.owner_attempt_outcome
|
|
248
|
+
)
|
|
249
|
+
SELECT inspected.account_id,
|
|
250
|
+
inspected.workspace_id,
|
|
251
|
+
inspected.session_id,
|
|
252
|
+
inspected.id,
|
|
253
|
+
inspected.reconcile_claim_id,
|
|
254
|
+
inspected.owner_state,
|
|
255
|
+
inspected.owner_attempt_outcome
|
|
256
|
+
FROM inspected
|
|
257
|
+
WHERE inspected.eligible
|
|
258
|
+
ORDER BY inspected.due_at, inspected.started_at, inspected.id;
|
|
259
|
+
|
|
260
|
+
-- Fire the exact data-schema constraint trigger while the definer still
|
|
261
|
+
-- owns cross-workspace visibility, then restore deferred mode.
|
|
262
|
+
SET CONSTRAINTS %1$I.sandbox_retained_processes_identity_v2 IMMEDIATE;
|
|
263
|
+
SET CONSTRAINTS %1$I.sandbox_retained_processes_identity_v2 DEFERRED;
|
|
264
|
+
END;
|
|
265
|
+
$function$;
|
|
266
|
+
$create$, data_schema);
|
|
267
|
+
|
|
268
|
+
-- Fixed-shape global inventories use covering partial indexes over only the
|
|
269
|
+
-- active/draining subsets; retained history and cold/warm lease inventory are
|
|
270
|
+
-- not scanned by a reaper tick. The application normalizes legacy values into
|
|
271
|
+
-- its finite label sets.
|
|
272
|
+
EXECUTE format($create$
|
|
273
|
+
CREATE OR REPLACE FUNCTION opengeni_private.count_active_retained_processes_by_owner_state()
|
|
274
|
+
RETURNS TABLE (owner_state text, active_count bigint, terminal_owner_count bigint)
|
|
275
|
+
LANGUAGE sql
|
|
276
|
+
SECURITY DEFINER
|
|
277
|
+
SET search_path = pg_catalog
|
|
278
|
+
AS $function$
|
|
279
|
+
SELECT inventory.owner_state,
|
|
280
|
+
pg_catalog.count(*)::bigint AS active_count,
|
|
281
|
+
pg_catalog.count(*) FILTER (WHERE inventory.terminal_owner)::bigint
|
|
282
|
+
AS terminal_owner_count
|
|
283
|
+
FROM (
|
|
284
|
+
SELECT 'direct'::text AS owner_state, true AS terminal_owner
|
|
285
|
+
FROM %1$I.sandbox_retained_processes process
|
|
286
|
+
WHERE process.state = 'active'
|
|
287
|
+
AND process.owner_actor_kind = 'direct'
|
|
288
|
+
UNION ALL
|
|
289
|
+
SELECT COALESCE(turn_row.status, 'missing') AS owner_state,
|
|
290
|
+
attempt.state = 'closed' AS terminal_owner
|
|
291
|
+
FROM %1$I.sandbox_retained_processes process
|
|
292
|
+
LEFT JOIN LATERAL (
|
|
293
|
+
SELECT source_turn.status
|
|
294
|
+
FROM %1$I.session_turns source_turn
|
|
295
|
+
WHERE source_turn.workspace_id = process.workspace_id
|
|
296
|
+
AND source_turn.id = process.owner_turn_id
|
|
297
|
+
LIMIT 1
|
|
298
|
+
) turn_row ON true
|
|
299
|
+
LEFT JOIN LATERAL (
|
|
300
|
+
SELECT source_attempt.state
|
|
301
|
+
FROM %1$I.session_turn_attempts source_attempt
|
|
302
|
+
WHERE source_attempt.workspace_id = process.workspace_id
|
|
303
|
+
AND source_attempt.id = process.owner_attempt_id
|
|
304
|
+
LIMIT 1
|
|
305
|
+
) attempt ON true
|
|
306
|
+
WHERE process.state = 'active'
|
|
307
|
+
AND process.owner_actor_kind = 'turn'
|
|
308
|
+
) inventory
|
|
309
|
+
GROUP BY inventory.owner_state;
|
|
310
|
+
$function$;
|
|
311
|
+
$create$, data_schema);
|
|
312
|
+
|
|
313
|
+
EXECUTE format($create$
|
|
314
|
+
CREATE OR REPLACE FUNCTION opengeni_private.count_expired_draining_sandbox_leases()
|
|
315
|
+
RETURNS TABLE (backend text, age_bucket text, count bigint)
|
|
316
|
+
LANGUAGE sql
|
|
317
|
+
SECURITY DEFINER
|
|
318
|
+
SET search_path = pg_catalog
|
|
319
|
+
AS $function$
|
|
320
|
+
SELECT lease.backend,
|
|
321
|
+
CASE
|
|
322
|
+
WHEN pg_catalog.now() - lease.expires_at < interval '5 minutes' THEN 'lt_5m'
|
|
323
|
+
WHEN pg_catalog.now() - lease.expires_at < interval '1 hour' THEN '5m_1h'
|
|
324
|
+
WHEN pg_catalog.now() - lease.expires_at < interval '1 day' THEN '1h_1d'
|
|
325
|
+
ELSE 'gte_1d'
|
|
326
|
+
END AS age_bucket,
|
|
327
|
+
pg_catalog.count(*)::bigint
|
|
328
|
+
FROM %1$I.sandbox_leases lease
|
|
329
|
+
WHERE lease.liveness = 'draining'
|
|
330
|
+
AND lease.expires_at < pg_catalog.now()
|
|
331
|
+
GROUP BY lease.backend, age_bucket;
|
|
332
|
+
$function$;
|
|
333
|
+
$create$, data_schema);
|
|
334
|
+
END $privileged_functions$;
|
|
335
|
+
|
|
336
|
+
-- PostgreSQL grants new functions to PUBLIC by default. These functions bypass
|
|
337
|
+
-- workspace RLS, so only the migration owner and explicit application role may
|
|
338
|
+
-- execute them.
|
|
339
|
+
REVOKE ALL ON FUNCTION opengeni_private.claim_terminal_retained_processes(uuid, integer, bigint)
|
|
340
|
+
FROM PUBLIC;
|
|
341
|
+
REVOKE ALL ON FUNCTION opengeni_private.count_active_retained_processes_by_owner_state()
|
|
342
|
+
FROM PUBLIC;
|
|
343
|
+
REVOKE ALL ON FUNCTION opengeni_private.count_expired_draining_sandbox_leases()
|
|
344
|
+
FROM PUBLIC;
|
|
345
|
+
|
|
346
|
+
DO $$
|
|
347
|
+
BEGIN
|
|
348
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
349
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.claim_terminal_retained_processes(uuid, integer, bigint)
|
|
350
|
+
TO opengeni_app;
|
|
351
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.count_active_retained_processes_by_owner_state()
|
|
352
|
+
TO opengeni_app;
|
|
353
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.count_expired_draining_sandbox_leases()
|
|
354
|
+
TO opengeni_app;
|
|
355
|
+
END IF;
|
|
356
|
+
END $$;
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Backend-only workspace instruction-policy foundation. This migration performs no
|
|
3
|
+
-- backfill: workspaces without an activation head retain legacy behavior.
|
|
4
|
+
|
|
5
|
+
CREATE SEQUENCE IF NOT EXISTS "workspace_instruction_policy_revision_seq" AS bigint;
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS "workspace_instruction_policy_revisions" (
|
|
8
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
9
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
10
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
11
|
+
"revision" bigint NOT NULL DEFAULT nextval('workspace_instruction_policy_revision_seq'),
|
|
12
|
+
"kind" text NOT NULL,
|
|
13
|
+
"scope" text NOT NULL,
|
|
14
|
+
"role_key" text,
|
|
15
|
+
"content" text NOT NULL,
|
|
16
|
+
"content_hash" text NOT NULL,
|
|
17
|
+
"provenance_source" text NOT NULL,
|
|
18
|
+
"provenance_source_id" text,
|
|
19
|
+
"supersedes_revision_id" uuid REFERENCES "workspace_instruction_policy_revisions"("id") ON DELETE RESTRICT,
|
|
20
|
+
"created_by_subject_id" text NOT NULL,
|
|
21
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
22
|
+
CONSTRAINT "workspace_instruction_policy_revisions_revision_chk" CHECK ("revision" > 0),
|
|
23
|
+
CONSTRAINT "workspace_instruction_policy_revisions_target_chk" CHECK (
|
|
24
|
+
("kind" = 'charter' AND "scope" = 'global' AND "role_key" IS NULL)
|
|
25
|
+
OR ("kind" = 'policy' AND "scope" = 'global' AND "role_key" IS NULL)
|
|
26
|
+
OR ("kind" = 'policy' AND "scope" = 'role' AND "role_key" IS NOT NULL)
|
|
27
|
+
),
|
|
28
|
+
CONSTRAINT "workspace_instruction_policy_revisions_role_key_chk" CHECK (
|
|
29
|
+
"role_key" IS NULL
|
|
30
|
+
OR (
|
|
31
|
+
"role_key" = lower(btrim("role_key"))
|
|
32
|
+
AND length("role_key") BETWEEN 1 AND 64
|
|
33
|
+
AND "role_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
|
|
34
|
+
AND "role_key" !~ '--'
|
|
35
|
+
)
|
|
36
|
+
),
|
|
37
|
+
CONSTRAINT "workspace_instruction_policy_revisions_content_chk" CHECK (
|
|
38
|
+
length(btrim("content")) > 0 AND octet_length("content") <= 1048576
|
|
39
|
+
),
|
|
40
|
+
CONSTRAINT "workspace_instruction_policy_revisions_hash_chk" CHECK (
|
|
41
|
+
"content_hash" ~ '^[0-9a-f]{64}$'
|
|
42
|
+
AND "content_hash" = encode(sha256(convert_to("content", 'UTF8')), 'hex')
|
|
43
|
+
),
|
|
44
|
+
CONSTRAINT "workspace_instruction_policy_revisions_provenance_chk" CHECK (
|
|
45
|
+
"provenance_source" IN ('human', 'onboarding', 'knowledge_proposal', 'legacy_import')
|
|
46
|
+
AND ("provenance_source_id" IS NULL OR length("provenance_source_id") BETWEEN 1 AND 512)
|
|
47
|
+
),
|
|
48
|
+
CONSTRAINT "workspace_instruction_policy_revisions_actor_chk" CHECK (
|
|
49
|
+
length(btrim("created_by_subject_id")) BETWEEN 1 AND 1024
|
|
50
|
+
),
|
|
51
|
+
CONSTRAINT "workspace_instruction_policy_revisions_workspace_revision_uq"
|
|
52
|
+
UNIQUE ("workspace_id", "revision")
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE INDEX IF NOT EXISTS "workspace_instruction_policy_revisions_workspace_history_idx"
|
|
56
|
+
ON "workspace_instruction_policy_revisions"
|
|
57
|
+
("workspace_id", "kind", "scope", "role_key", "revision" DESC);
|
|
58
|
+
|
|
59
|
+
CREATE TABLE IF NOT EXISTS "workspace_instruction_policy_heads" (
|
|
60
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
61
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
62
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
63
|
+
"kind" text NOT NULL,
|
|
64
|
+
"scope" text NOT NULL,
|
|
65
|
+
"role_key" text,
|
|
66
|
+
"revision_id" uuid NOT NULL REFERENCES "workspace_instruction_policy_revisions"("id") ON DELETE RESTRICT,
|
|
67
|
+
"revision" bigint NOT NULL,
|
|
68
|
+
"content_hash" text NOT NULL,
|
|
69
|
+
"activation_version" bigint NOT NULL,
|
|
70
|
+
"activated_at" timestamptz NOT NULL DEFAULT now(),
|
|
71
|
+
CONSTRAINT "workspace_instruction_policy_heads_target_chk" CHECK (
|
|
72
|
+
("kind" = 'charter' AND "scope" = 'global' AND "role_key" IS NULL)
|
|
73
|
+
OR ("kind" = 'policy' AND "scope" = 'global' AND "role_key" IS NULL)
|
|
74
|
+
OR ("kind" = 'policy' AND "scope" = 'role' AND "role_key" IS NOT NULL)
|
|
75
|
+
),
|
|
76
|
+
CONSTRAINT "workspace_instruction_policy_heads_role_key_chk" CHECK (
|
|
77
|
+
"role_key" IS NULL
|
|
78
|
+
OR (
|
|
79
|
+
"role_key" = lower(btrim("role_key"))
|
|
80
|
+
AND length("role_key") BETWEEN 1 AND 64
|
|
81
|
+
AND "role_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
|
|
82
|
+
AND "role_key" !~ '--'
|
|
83
|
+
)
|
|
84
|
+
),
|
|
85
|
+
CONSTRAINT "workspace_instruction_policy_heads_revision_chk" CHECK (
|
|
86
|
+
"revision" > 0 AND "activation_version" > 0 AND "content_hash" ~ '^[0-9a-f]{64}$'
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspace_instruction_policy_heads_charter_uq"
|
|
91
|
+
ON "workspace_instruction_policy_heads" ("workspace_id")
|
|
92
|
+
WHERE "kind" = 'charter';
|
|
93
|
+
|
|
94
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspace_instruction_policy_heads_global_policy_uq"
|
|
95
|
+
ON "workspace_instruction_policy_heads" ("workspace_id")
|
|
96
|
+
WHERE "kind" = 'policy' AND "scope" = 'global';
|
|
97
|
+
|
|
98
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspace_instruction_policy_heads_role_policy_uq"
|
|
99
|
+
ON "workspace_instruction_policy_heads" ("workspace_id", "role_key")
|
|
100
|
+
WHERE "kind" = 'policy' AND "scope" = 'role';
|
|
101
|
+
|
|
102
|
+
CREATE TABLE IF NOT EXISTS "workspace_instruction_policy_activation_events" (
|
|
103
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
104
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
105
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
106
|
+
"kind" text NOT NULL,
|
|
107
|
+
"scope" text NOT NULL,
|
|
108
|
+
"role_key" text,
|
|
109
|
+
"type" text NOT NULL,
|
|
110
|
+
"activation_version" bigint NOT NULL,
|
|
111
|
+
"old_revision_id" uuid REFERENCES "workspace_instruction_policy_revisions"("id") ON DELETE RESTRICT,
|
|
112
|
+
"old_revision" bigint,
|
|
113
|
+
"old_content_hash" text,
|
|
114
|
+
"new_revision_id" uuid NOT NULL REFERENCES "workspace_instruction_policy_revisions"("id") ON DELETE RESTRICT,
|
|
115
|
+
"new_revision" bigint NOT NULL,
|
|
116
|
+
"new_content_hash" text NOT NULL,
|
|
117
|
+
"actor_subject_id" text NOT NULL,
|
|
118
|
+
"reason" text NOT NULL,
|
|
119
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
120
|
+
CONSTRAINT "workspace_instruction_policy_activation_events_type_chk" CHECK (
|
|
121
|
+
"type" IN ('activate', 'rollback')
|
|
122
|
+
),
|
|
123
|
+
CONSTRAINT "workspace_instruction_policy_activation_events_target_chk" CHECK (
|
|
124
|
+
("kind" = 'charter' AND "scope" = 'global' AND "role_key" IS NULL)
|
|
125
|
+
OR ("kind" = 'policy' AND "scope" = 'global' AND "role_key" IS NULL)
|
|
126
|
+
OR ("kind" = 'policy' AND "scope" = 'role' AND "role_key" IS NOT NULL)
|
|
127
|
+
),
|
|
128
|
+
CONSTRAINT "workspace_instruction_policy_activation_events_role_key_chk" CHECK (
|
|
129
|
+
"role_key" IS NULL
|
|
130
|
+
OR (
|
|
131
|
+
"role_key" = lower(btrim("role_key"))
|
|
132
|
+
AND length("role_key") BETWEEN 1 AND 64
|
|
133
|
+
AND "role_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
|
|
134
|
+
AND "role_key" !~ '--'
|
|
135
|
+
)
|
|
136
|
+
),
|
|
137
|
+
CONSTRAINT "workspace_instruction_policy_activation_events_old_revision_chk" CHECK (
|
|
138
|
+
("old_revision_id" IS NULL AND "old_revision" IS NULL AND "old_content_hash" IS NULL)
|
|
139
|
+
OR (
|
|
140
|
+
"old_revision_id" IS NOT NULL
|
|
141
|
+
AND "old_revision" > 0
|
|
142
|
+
AND "old_content_hash" ~ '^[0-9a-f]{64}$'
|
|
143
|
+
)
|
|
144
|
+
),
|
|
145
|
+
CONSTRAINT "workspace_instruction_policy_activation_events_new_revision_chk" CHECK (
|
|
146
|
+
"new_revision" > 0
|
|
147
|
+
AND "new_content_hash" ~ '^[0-9a-f]{64}$'
|
|
148
|
+
AND "activation_version" > 0
|
|
149
|
+
),
|
|
150
|
+
CONSTRAINT "workspace_instruction_policy_activation_events_audit_chk" CHECK (
|
|
151
|
+
length(btrim("actor_subject_id")) BETWEEN 1 AND 1024
|
|
152
|
+
AND length(btrim("reason")) BETWEEN 1 AND 4096
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workspace_instruction_policy_events_target_version_uq"
|
|
157
|
+
ON "workspace_instruction_policy_activation_events"
|
|
158
|
+
("workspace_id", "kind", "scope", coalesce("role_key", ''), "activation_version");
|
|
159
|
+
|
|
160
|
+
CREATE INDEX IF NOT EXISTS "workspace_instruction_policy_events_workspace_time_idx"
|
|
161
|
+
ON "workspace_instruction_policy_activation_events" ("workspace_id", "created_at" DESC, "id");
|
|
162
|
+
|
|
163
|
+
CREATE OR REPLACE FUNCTION workspace_instruction_policy_validate_revision_link()
|
|
164
|
+
RETURNS trigger
|
|
165
|
+
LANGUAGE plpgsql
|
|
166
|
+
AS $$
|
|
167
|
+
DECLARE
|
|
168
|
+
target_workspace_id uuid;
|
|
169
|
+
target_kind text;
|
|
170
|
+
target_scope text;
|
|
171
|
+
target_role_key text;
|
|
172
|
+
BEGIN
|
|
173
|
+
IF NEW."supersedes_revision_id" IS NULL THEN
|
|
174
|
+
RETURN NEW;
|
|
175
|
+
END IF;
|
|
176
|
+
SELECT "workspace_id", "kind", "scope", "role_key"
|
|
177
|
+
INTO target_workspace_id, target_kind, target_scope, target_role_key
|
|
178
|
+
FROM "workspace_instruction_policy_revisions"
|
|
179
|
+
WHERE "id" = NEW."supersedes_revision_id";
|
|
180
|
+
IF NOT FOUND
|
|
181
|
+
OR target_workspace_id IS DISTINCT FROM NEW."workspace_id"
|
|
182
|
+
OR target_kind IS DISTINCT FROM NEW."kind"
|
|
183
|
+
OR target_scope IS DISTINCT FROM NEW."scope"
|
|
184
|
+
OR target_role_key IS DISTINCT FROM NEW."role_key"
|
|
185
|
+
THEN
|
|
186
|
+
RAISE EXCEPTION 'superseded instruction-policy revision must use the same workspace and target'
|
|
187
|
+
USING ERRCODE = '23514';
|
|
188
|
+
END IF;
|
|
189
|
+
RETURN NEW;
|
|
190
|
+
END;
|
|
191
|
+
$$;
|
|
192
|
+
|
|
193
|
+
DROP TRIGGER IF EXISTS workspace_instruction_policy_revisions_validate_link
|
|
194
|
+
ON "workspace_instruction_policy_revisions";
|
|
195
|
+
CREATE TRIGGER workspace_instruction_policy_revisions_validate_link
|
|
196
|
+
BEFORE INSERT OR UPDATE ON "workspace_instruction_policy_revisions"
|
|
197
|
+
FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_validate_revision_link();
|
|
198
|
+
|
|
199
|
+
CREATE OR REPLACE FUNCTION workspace_instruction_policy_validate_head()
|
|
200
|
+
RETURNS trigger
|
|
201
|
+
LANGUAGE plpgsql
|
|
202
|
+
AS $$
|
|
203
|
+
BEGIN
|
|
204
|
+
IF NOT EXISTS (
|
|
205
|
+
SELECT 1
|
|
206
|
+
FROM "workspace_instruction_policy_revisions" revision
|
|
207
|
+
WHERE revision."id" = NEW."revision_id"
|
|
208
|
+
AND revision."account_id" = NEW."account_id"
|
|
209
|
+
AND revision."workspace_id" = NEW."workspace_id"
|
|
210
|
+
AND revision."kind" = NEW."kind"
|
|
211
|
+
AND revision."scope" = NEW."scope"
|
|
212
|
+
AND revision."role_key" IS NOT DISTINCT FROM NEW."role_key"
|
|
213
|
+
AND revision."revision" = NEW."revision"
|
|
214
|
+
AND revision."content_hash" = NEW."content_hash"
|
|
215
|
+
) THEN
|
|
216
|
+
RAISE EXCEPTION 'instruction-policy head must identify an exact target revision'
|
|
217
|
+
USING ERRCODE = '23514';
|
|
218
|
+
END IF;
|
|
219
|
+
RETURN NEW;
|
|
220
|
+
END;
|
|
221
|
+
$$;
|
|
222
|
+
|
|
223
|
+
DROP TRIGGER IF EXISTS workspace_instruction_policy_heads_validate_revision
|
|
224
|
+
ON "workspace_instruction_policy_heads";
|
|
225
|
+
CREATE TRIGGER workspace_instruction_policy_heads_validate_revision
|
|
226
|
+
BEFORE INSERT OR UPDATE ON "workspace_instruction_policy_heads"
|
|
227
|
+
FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_validate_head();
|
|
228
|
+
|
|
229
|
+
CREATE OR REPLACE FUNCTION workspace_instruction_policy_validate_event()
|
|
230
|
+
RETURNS trigger
|
|
231
|
+
LANGUAGE plpgsql
|
|
232
|
+
AS $$
|
|
233
|
+
BEGIN
|
|
234
|
+
IF NOT EXISTS (
|
|
235
|
+
SELECT 1
|
|
236
|
+
FROM "workspace_instruction_policy_revisions" revision
|
|
237
|
+
WHERE revision."id" = NEW."new_revision_id"
|
|
238
|
+
AND revision."account_id" = NEW."account_id"
|
|
239
|
+
AND revision."workspace_id" = NEW."workspace_id"
|
|
240
|
+
AND revision."kind" = NEW."kind"
|
|
241
|
+
AND revision."scope" = NEW."scope"
|
|
242
|
+
AND revision."role_key" IS NOT DISTINCT FROM NEW."role_key"
|
|
243
|
+
AND revision."revision" = NEW."new_revision"
|
|
244
|
+
AND revision."content_hash" = NEW."new_content_hash"
|
|
245
|
+
) THEN
|
|
246
|
+
RAISE EXCEPTION 'instruction-policy activation event has an invalid new revision'
|
|
247
|
+
USING ERRCODE = '23514';
|
|
248
|
+
END IF;
|
|
249
|
+
IF NEW."old_revision_id" IS NOT NULL AND NOT EXISTS (
|
|
250
|
+
SELECT 1
|
|
251
|
+
FROM "workspace_instruction_policy_revisions" revision
|
|
252
|
+
WHERE revision."id" = NEW."old_revision_id"
|
|
253
|
+
AND revision."account_id" = NEW."account_id"
|
|
254
|
+
AND revision."workspace_id" = NEW."workspace_id"
|
|
255
|
+
AND revision."kind" = NEW."kind"
|
|
256
|
+
AND revision."scope" = NEW."scope"
|
|
257
|
+
AND revision."role_key" IS NOT DISTINCT FROM NEW."role_key"
|
|
258
|
+
AND revision."revision" = NEW."old_revision"
|
|
259
|
+
AND revision."content_hash" = NEW."old_content_hash"
|
|
260
|
+
) THEN
|
|
261
|
+
RAISE EXCEPTION 'instruction-policy activation event has an invalid old revision'
|
|
262
|
+
USING ERRCODE = '23514';
|
|
263
|
+
END IF;
|
|
264
|
+
RETURN NEW;
|
|
265
|
+
END;
|
|
266
|
+
$$;
|
|
267
|
+
|
|
268
|
+
DROP TRIGGER IF EXISTS workspace_instruction_policy_events_validate_revisions
|
|
269
|
+
ON "workspace_instruction_policy_activation_events";
|
|
270
|
+
CREATE TRIGGER workspace_instruction_policy_events_validate_revisions
|
|
271
|
+
BEFORE INSERT OR UPDATE ON "workspace_instruction_policy_activation_events"
|
|
272
|
+
FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_validate_event();
|
|
273
|
+
|
|
274
|
+
CREATE OR REPLACE FUNCTION workspace_instruction_policy_reject_mutation()
|
|
275
|
+
RETURNS trigger
|
|
276
|
+
LANGUAGE plpgsql
|
|
277
|
+
AS $$
|
|
278
|
+
BEGIN
|
|
279
|
+
-- PostgreSQL implements ON DELETE CASCADE with a parent constraint trigger.
|
|
280
|
+
-- By the time that trigger deletes child rows, the workspace is absent and
|
|
281
|
+
-- this child trigger is nested. Require both facts so direct history deletes
|
|
282
|
+
-- and deletes issued by unrelated triggers remain fail-closed.
|
|
283
|
+
IF TG_OP = 'DELETE'
|
|
284
|
+
AND pg_trigger_depth() > 1
|
|
285
|
+
AND NOT EXISTS (
|
|
286
|
+
SELECT 1
|
|
287
|
+
FROM "workspaces" workspace
|
|
288
|
+
WHERE workspace."id" = OLD."workspace_id"
|
|
289
|
+
)
|
|
290
|
+
THEN
|
|
291
|
+
RETURN OLD;
|
|
292
|
+
END IF;
|
|
293
|
+
RAISE EXCEPTION 'workspace instruction-policy history is immutable'
|
|
294
|
+
USING ERRCODE = '55000';
|
|
295
|
+
END;
|
|
296
|
+
$$;
|
|
297
|
+
|
|
298
|
+
DROP TRIGGER IF EXISTS workspace_instruction_policy_revisions_immutable
|
|
299
|
+
ON "workspace_instruction_policy_revisions";
|
|
300
|
+
CREATE TRIGGER workspace_instruction_policy_revisions_immutable
|
|
301
|
+
BEFORE UPDATE OR DELETE ON "workspace_instruction_policy_revisions"
|
|
302
|
+
FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_reject_mutation();
|
|
303
|
+
|
|
304
|
+
DROP TRIGGER IF EXISTS workspace_instruction_policy_events_immutable
|
|
305
|
+
ON "workspace_instruction_policy_activation_events";
|
|
306
|
+
CREATE TRIGGER workspace_instruction_policy_events_immutable
|
|
307
|
+
BEFORE UPDATE OR DELETE ON "workspace_instruction_policy_activation_events"
|
|
308
|
+
FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_reject_mutation();
|
|
309
|
+
|
|
310
|
+
ALTER TABLE "workspace_instruction_policy_revisions" ENABLE ROW LEVEL SECURITY;
|
|
311
|
+
ALTER TABLE "workspace_instruction_policy_revisions" FORCE ROW LEVEL SECURITY;
|
|
312
|
+
ALTER TABLE "workspace_instruction_policy_heads" ENABLE ROW LEVEL SECURITY;
|
|
313
|
+
ALTER TABLE "workspace_instruction_policy_heads" FORCE ROW LEVEL SECURITY;
|
|
314
|
+
ALTER TABLE "workspace_instruction_policy_activation_events" ENABLE ROW LEVEL SECURITY;
|
|
315
|
+
ALTER TABLE "workspace_instruction_policy_activation_events" FORCE ROW LEVEL SECURITY;
|
|
316
|
+
|
|
317
|
+
DROP POLICY IF EXISTS workspace_isolation ON "workspace_instruction_policy_revisions";
|
|
318
|
+
CREATE POLICY workspace_isolation ON "workspace_instruction_policy_revisions"
|
|
319
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
320
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
321
|
+
|
|
322
|
+
DROP POLICY IF EXISTS workspace_isolation ON "workspace_instruction_policy_heads";
|
|
323
|
+
CREATE POLICY workspace_isolation ON "workspace_instruction_policy_heads"
|
|
324
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
325
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
326
|
+
|
|
327
|
+
DROP POLICY IF EXISTS workspace_isolation ON "workspace_instruction_policy_activation_events";
|
|
328
|
+
CREATE POLICY workspace_isolation ON "workspace_instruction_policy_activation_events"
|
|
329
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
330
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|