@opengeni/db 0.14.3 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/{chunk-Y5WZZVQK.js → chunk-SK7YYHBI.js} +5 -2
  2. package/dist/chunk-SK7YYHBI.js.map +1 -0
  3. package/dist/{chunk-ELMUCYZF.js → chunk-T6RSJT6C.js} +202 -43
  4. package/dist/chunk-T6RSJT6C.js.map +1 -0
  5. package/dist/chunk-TLAC622R.js +4990 -0
  6. package/dist/chunk-TLAC622R.js.map +1 -0
  7. package/dist/codex-token-resolver.d.ts +64 -0
  8. package/dist/connection-token-resolver.d.ts +90 -0
  9. package/dist/environment-crypto.d.ts +11 -0
  10. package/dist/event-payload-sanitizer.d.ts +32 -0
  11. package/dist/index.d.ts +6374 -10
  12. package/dist/index.js +8075 -2776
  13. package/dist/index.js.map +1 -1
  14. package/dist/insights.d.ts +157 -0
  15. package/dist/memory-domain.d.ts +44 -0
  16. package/dist/migrate.d.ts +5 -7
  17. package/dist/migrate.js +1 -1
  18. package/dist/new-session-drafts.d.ts +49 -0
  19. package/dist/persistence-errors.d.ts +69 -0
  20. package/dist/preference-registry-schema.d.ts +1147 -0
  21. package/dist/preference-registry.d.ts +878 -0
  22. package/dist/provision-roles.d.ts +4 -6489
  23. package/dist/provision-roles.js +1 -1
  24. package/dist/role-relationships.d.ts +35 -0
  25. package/dist/runtime-posture-cli.d.ts +1 -0
  26. package/dist/runtime-posture.d.ts +103 -0
  27. package/dist/schema.d.ts +25330 -3
  28. package/dist/schema.js +31 -1
  29. package/dist/session-control.d.ts +331 -0
  30. package/dist/session-queue-commands.d.ts +186 -0
  31. package/dist/session-tool-call-settlement.d.ts +32 -0
  32. package/dist/turn-initiator.d.ts +28 -0
  33. package/dist/workspace-artifacts.d.ts +62 -0
  34. package/dist/workspace-instruction-policies-schema.d.ts +735 -0
  35. package/dist/workspace-instruction-policies.d.ts +64 -0
  36. package/drizzle/0136_unified_session_tool_policy.sql +1 -1
  37. package/drizzle/0137_preference_registry.sql +1347 -0
  38. package/drizzle/0138_sandbox_checkpoint_artifacts_and_deadlines.sql +1074 -0
  39. package/drizzle/0139_codex_provider_artifact_invalidations.sql +51 -0
  40. package/drizzle/0140_sandbox_restore_and_reaper_fences.sql +276 -0
  41. package/drizzle/0142_sandbox_archive_capture_gate.sql +80 -0
  42. package/drizzle/0143_session_codex_compaction_mode.sql +20 -0
  43. package/drizzle/0144_sandbox_viewer_force_drain_gate.sql +95 -0
  44. package/drizzle/0145_model_call_facts.sql +96 -0
  45. package/drizzle/0146_slack_bot_delete_idempotency.sql +105 -0
  46. package/drizzle/0147_draft_latency_mode.sql +12 -0
  47. package/drizzle/0148_session_turn_latency_mode.sql +12 -0
  48. package/drizzle/0149_workspace_artifacts.sql +348 -0
  49. package/drizzle/0150_slack_task_interactions.sql +344 -0
  50. package/package.json +6 -6
  51. package/src/index.ts +5766 -275
  52. package/src/insights.ts +837 -0
  53. package/src/migrate.ts +9 -1
  54. package/src/new-session-drafts.ts +4 -0
  55. package/src/preference-registry-schema.ts +170 -0
  56. package/src/preference-registry.ts +1220 -0
  57. package/src/provision-roles.ts +95 -22
  58. package/src/role-relationships.ts +132 -0
  59. package/src/runtime-posture.ts +47 -27
  60. package/src/schema.ts +875 -19
  61. package/src/session-queue-commands.ts +39 -6
  62. package/src/workspace-artifacts.ts +751 -0
  63. package/dist/chunk-DW24CKCT.js +0 -4046
  64. package/dist/chunk-DW24CKCT.js.map +0 -1
  65. package/dist/chunk-ELMUCYZF.js.map +0 -1
  66. package/dist/chunk-Y5WZZVQK.js.map +0 -1
  67. package/dist/schema-DhkRhcuQ.d.ts +0 -22666
@@ -0,0 +1,105 @@
1
+ -- deployment-mode: rolling
2
+ -- Slack chat.delete has no provider idempotency key. Persist the exact tenant,
3
+ -- principal, connection, tool, target, and protected request digest before the
4
+ -- mutation; an abandoned provider_started claim becomes outcome_unknown and is
5
+ -- reconciled before another delete is admitted.
6
+ --
7
+ -- This migration originally landed as 0141 after 0142 was already deployed.
8
+ -- Never rewrite that production history. A staging database may have applied
9
+ -- the withdrawn 0141 name before the ordering defect was caught; only that
10
+ -- exact committed legacy receipt may make an existing table idempotent here.
11
+
12
+ DO $migration$
13
+ BEGIN
14
+ IF to_regclass('slack_bot_delete_operations') IS NOT NULL
15
+ AND NOT EXISTS (
16
+ SELECT 1
17
+ FROM "schema_migrations"
18
+ WHERE "name" = '0141_slack_bot_delete_idempotency.sql'
19
+ )
20
+ THEN
21
+ RAISE EXCEPTION
22
+ 'slack_bot_delete_operations exists without the exact withdrawn 0141 migration receipt';
23
+ END IF;
24
+ END
25
+ $migration$;
26
+
27
+ CREATE TABLE IF NOT EXISTS "slack_bot_delete_operations" (
28
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
29
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
30
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
31
+ "connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
32
+ "operation_id" uuid NOT NULL,
33
+ "principal_type" text NOT NULL,
34
+ "principal_id" text NOT NULL,
35
+ "tool_name" text NOT NULL,
36
+ "channel_id" text NOT NULL,
37
+ "message_timestamp" text NOT NULL,
38
+ "request_digest" text NOT NULL,
39
+ "status" text NOT NULL,
40
+ "claim_holder_id" uuid,
41
+ "claim_expires_at" timestamptz,
42
+ "attempt_count" integer NOT NULL DEFAULT 0,
43
+ "last_failure_code" text,
44
+ "slack_channel_id" text,
45
+ "slack_message_timestamp" text,
46
+ "completed_at" timestamptz,
47
+ "created_at" timestamptz NOT NULL DEFAULT now(),
48
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
49
+ CONSTRAINT "slack_bot_delete_operations_workspace_operation_uq"
50
+ UNIQUE ("workspace_id", "operation_id"),
51
+ CONSTRAINT "slack_bot_delete_operations_principal_check"
52
+ CHECK (
53
+ "principal_type" IN ('subject', 'service')
54
+ AND length("principal_id") BETWEEN 1 AND 512
55
+ ),
56
+ CONSTRAINT "slack_bot_delete_operations_identity_check"
57
+ CHECK (
58
+ "tool_name" = 'slack_bot_delete_message'
59
+ AND length("channel_id") BETWEEN 1 AND 64
60
+ AND length("message_timestamp") BETWEEN 1 AND 64
61
+ AND "request_digest" ~ '^[0-9a-f]{64}$'
62
+ AND "attempt_count" > 0
63
+ AND (("claim_holder_id" IS NULL) = ("claim_expires_at" IS NULL))
64
+ ),
65
+ CONSTRAINT "slack_bot_delete_operations_status_check"
66
+ CHECK ("status" IN ('pending', 'provider_started', 'outcome_unknown', 'completed')),
67
+ CONSTRAINT "slack_bot_delete_operations_completion_check"
68
+ CHECK (
69
+ (
70
+ "status" <> 'completed'
71
+ AND "slack_channel_id" IS NULL
72
+ AND "slack_message_timestamp" IS NULL
73
+ AND "completed_at" IS NULL
74
+ )
75
+ OR (
76
+ "status" = 'completed'
77
+ AND "claim_holder_id" IS NULL
78
+ AND "claim_expires_at" IS NULL
79
+ AND "slack_channel_id" IS NOT NULL
80
+ AND "slack_message_timestamp" IS NOT NULL
81
+ AND "completed_at" IS NOT NULL
82
+ )
83
+ )
84
+ );
85
+
86
+ CREATE INDEX IF NOT EXISTS "slack_bot_delete_operations_workspace_status_idx"
87
+ ON "slack_bot_delete_operations" ("workspace_id", "status", "updated_at");
88
+
89
+ ALTER TABLE "slack_bot_delete_operations" ENABLE ROW LEVEL SECURITY;
90
+ ALTER TABLE "slack_bot_delete_operations" FORCE ROW LEVEL SECURITY;
91
+ DO $migration$
92
+ BEGIN
93
+ IF NOT EXISTS (
94
+ SELECT 1
95
+ FROM pg_policy
96
+ WHERE polrelid = 'slack_bot_delete_operations'::regclass
97
+ AND polname = 'workspace_isolation'
98
+ )
99
+ THEN
100
+ CREATE POLICY workspace_isolation ON "slack_bot_delete_operations"
101
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
102
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
103
+ END IF;
104
+ END
105
+ $migration$;
@@ -0,0 +1,12 @@
1
+ -- deployment-mode: rolling
2
+ -- Draft latency selection is additive; existing rows remain Standard.
3
+
4
+ ALTER TABLE composer_drafts
5
+ ADD COLUMN latency_mode text NOT NULL DEFAULT 'standard',
6
+ ADD CONSTRAINT composer_drafts_latency_mode_check
7
+ CHECK (latency_mode IN ('standard', 'priority', 'fast'));
8
+
9
+ ALTER TABLE new_session_drafts
10
+ ADD COLUMN latency_mode text NOT NULL DEFAULT 'standard',
11
+ ADD CONSTRAINT new_session_drafts_latency_mode_check
12
+ CHECK (latency_mode IN ('standard', 'priority', 'fast'));
@@ -0,0 +1,12 @@
1
+ -- deployment-mode: rolling
2
+ -- Per-turn latency mode is the durable peer of model + reasoning_effort.
3
+ -- Existing rows default to Standard; backfill from frozen turn execution policy.
4
+
5
+ ALTER TABLE session_turns
6
+ ADD COLUMN latency_mode text NOT NULL DEFAULT 'standard',
7
+ ADD CONSTRAINT session_turns_latency_mode_check
8
+ CHECK (latency_mode IN ('standard', 'priority', 'fast'));
9
+
10
+ UPDATE session_turns
11
+ SET latency_mode = metadata->'turnExecutionPolicyV1'->>'latencyMode'
12
+ WHERE metadata->'turnExecutionPolicyV1'->>'latencyMode' IN ('standard', 'priority', 'fast');
@@ -0,0 +1,348 @@
1
+ -- deployment-mode: maintenance
2
+ -- Generic, versioned workspace-published HTML artifacts. Existing sessions
3
+ -- retain their immutable capability policy; only newly created sessions use
4
+ -- the artifact tools added to the application default catalog.
5
+
6
+ -- NULL meant "the then-current worker default" before artifact permissions
7
+ -- existed. Freeze that exact historical default before the application default
8
+ -- widens. Explicit [] and every explicit narrow array remain byte-for-byte
9
+ -- unchanged. first_party_mcp_tools has been explicit and NOT NULL since 0136;
10
+ -- do not append the new artifact tools to any historical selection.
11
+ UPDATE "sessions"
12
+ SET "first_party_mcp_permissions" = '[
13
+ "workspace:read",
14
+ "files:read",
15
+ "documents:search",
16
+ "scheduled_tasks:manage",
17
+ "scheduled_tasks:run",
18
+ "goals:manage",
19
+ "sessions:read",
20
+ "sessions:create",
21
+ "sessions:control",
22
+ "variable-sets:use",
23
+ "variable-sets:manage",
24
+ "rigs:use",
25
+ "github:use"
26
+ ]'::jsonb
27
+ WHERE "first_party_mcp_permissions" IS NULL;
28
+
29
+ ALTER TABLE "sessions"
30
+ ALTER COLUMN "first_party_mcp_tools"
31
+ SET DEFAULT '[
32
+ "set_session_title",
33
+ "goal_set",
34
+ "goal_update",
35
+ "goal_complete",
36
+ "goal_pause",
37
+ "memory_search",
38
+ "memory_save",
39
+ "memory_correct",
40
+ "preference_registry_summary",
41
+ "preference_registry_get",
42
+ "sandboxes_list",
43
+ "sandbox_attach",
44
+ "sandbox_swap",
45
+ "run_on",
46
+ "sandbox_provision",
47
+ "rig_list",
48
+ "rig_get",
49
+ "rig_propose_change",
50
+ "rig_verify",
51
+ "rig_promote",
52
+ "sessions_list",
53
+ "session_get",
54
+ "session_events",
55
+ "session_create",
56
+ "session_send_message",
57
+ "session_pause",
58
+ "session_resume",
59
+ "session_steer",
60
+ "set_other_session_title",
61
+ "variable_set_list",
62
+ "environment_list",
63
+ "variable_set_set_variable",
64
+ "environment_set_variable",
65
+ "github_connect_link",
66
+ "github_token",
67
+ "github_repositories_list",
68
+ "social_connections_list",
69
+ "social_posts_recent",
70
+ "social_daily_analysis_context",
71
+ "scheduled_tasks_list",
72
+ "scheduled_tasks_get",
73
+ "scheduled_tasks_create",
74
+ "scheduled_tasks_update",
75
+ "scheduled_tasks_pause",
76
+ "scheduled_tasks_resume",
77
+ "scheduled_tasks_trigger",
78
+ "scheduled_tasks_delete",
79
+ "scheduled_task_runs_list",
80
+ "slack_bot_list_channels",
81
+ "slack_bot_channel_history",
82
+ "slack_bot_thread_replies",
83
+ "slack_bot_list_users",
84
+ "slack_bot_list_files",
85
+ "slack_bot_file_info",
86
+ "slack_bot_file_content",
87
+ "slack_bot_post_message",
88
+ "slack_bot_delete_message",
89
+ "artifacts_list",
90
+ "artifacts_get_source",
91
+ "artifacts_create",
92
+ "artifacts_publish",
93
+ "artifacts_rollback"
94
+ ]'::jsonb;
95
+
96
+ CREATE TABLE "workspace_artifacts" (
97
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
98
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
99
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
100
+ "slug" text NOT NULL,
101
+ "title" text NOT NULL,
102
+ "description" text,
103
+ "status" text NOT NULL DEFAULT 'active',
104
+ "current_version_id" uuid,
105
+ "created_by_subject_id" text NOT NULL,
106
+ "created_at" timestamptz NOT NULL DEFAULT now(),
107
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
108
+ CONSTRAINT "workspace_artifacts_status_chk" CHECK ("status" IN ('active', 'archived')),
109
+ CONSTRAINT "workspace_artifacts_slug_chk" CHECK (
110
+ length("slug") BETWEEN 1 AND 96
111
+ AND "slug" ~ '^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$'
112
+ ),
113
+ CONSTRAINT "workspace_artifacts_title_chk" CHECK (length(btrim("title")) BETWEEN 1 AND 120),
114
+ CONSTRAINT "workspace_artifacts_description_chk" CHECK (
115
+ "description" IS NULL OR length("description") <= 2000
116
+ ),
117
+ CONSTRAINT "workspace_artifacts_actor_chk" CHECK (
118
+ length(btrim("created_by_subject_id")) BETWEEN 1 AND 1024
119
+ ),
120
+ CONSTRAINT "workspace_artifacts_workspace_account_fk"
121
+ FOREIGN KEY ("workspace_id", "account_id")
122
+ REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
123
+ CONSTRAINT "workspace_artifacts_workspace_id_uq" UNIQUE ("workspace_id", "id"),
124
+ CONSTRAINT "workspace_artifacts_workspace_slug_uq" UNIQUE ("workspace_id", "slug")
125
+ );
126
+
127
+ CREATE INDEX "workspace_artifacts_list_idx"
128
+ ON "workspace_artifacts" ("workspace_id", "updated_at" DESC);
129
+
130
+ CREATE TABLE "workspace_artifact_versions" (
131
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
132
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
133
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
134
+ "artifact_id" uuid NOT NULL,
135
+ "revision" integer NOT NULL,
136
+ "content_key" text NOT NULL,
137
+ "content_type" text NOT NULL DEFAULT 'text/html',
138
+ "content_sha256" text NOT NULL,
139
+ "size_bytes" integer NOT NULL,
140
+ "operation_key" text NOT NULL,
141
+ "source_session_id" uuid,
142
+ "source_turn_id" uuid,
143
+ "source_attempt_id" uuid,
144
+ "source_execution_generation" integer,
145
+ "created_by_subject_id" text NOT NULL,
146
+ "created_at" timestamptz NOT NULL DEFAULT now(),
147
+ CONSTRAINT "workspace_artifact_versions_artifact_fk"
148
+ FOREIGN KEY ("workspace_id", "artifact_id")
149
+ REFERENCES "workspace_artifacts"("workspace_id", "id") ON DELETE CASCADE,
150
+ CONSTRAINT "workspace_artifact_versions_workspace_account_fk"
151
+ FOREIGN KEY ("workspace_id", "account_id")
152
+ REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
153
+ CONSTRAINT "workspace_artifact_versions_workspace_id_uq" UNIQUE ("workspace_id", "id"),
154
+ CONSTRAINT "workspace_artifact_versions_revision_uq"
155
+ UNIQUE ("workspace_id", "artifact_id", "revision"),
156
+ CONSTRAINT "workspace_artifact_versions_operation_uq" UNIQUE ("workspace_id", "operation_key"),
157
+ CONSTRAINT "workspace_artifact_versions_revision_chk" CHECK ("revision" > 0),
158
+ CONSTRAINT "workspace_artifact_versions_content_chk" CHECK (
159
+ "content_type" = 'text/html'
160
+ AND "content_sha256" ~ '^[0-9a-f]{64}$'
161
+ AND "size_bytes" BETWEEN 1 AND 524288
162
+ AND length("content_key") BETWEEN 1 AND 1024
163
+ ),
164
+ CONSTRAINT "workspace_artifact_versions_operation_chk" CHECK (
165
+ length("operation_key") BETWEEN 1 AND 512
166
+ ),
167
+ CONSTRAINT "workspace_artifact_versions_actor_chk" CHECK (
168
+ length(btrim("created_by_subject_id")) BETWEEN 1 AND 1024
169
+ ),
170
+ CONSTRAINT "workspace_artifact_versions_provenance_chk" CHECK (
171
+ (
172
+ "source_session_id" IS NULL
173
+ AND "source_turn_id" IS NULL
174
+ AND "source_attempt_id" IS NULL
175
+ AND "source_execution_generation" IS NULL
176
+ ) OR (
177
+ "source_session_id" IS NOT NULL
178
+ AND "source_turn_id" IS NOT NULL
179
+ AND "source_attempt_id" IS NOT NULL
180
+ AND "source_execution_generation" > 0
181
+ )
182
+ )
183
+ );
184
+
185
+ ALTER TABLE "workspace_artifacts"
186
+ ADD CONSTRAINT "workspace_artifacts_current_version_fk"
187
+ FOREIGN KEY ("workspace_id", "current_version_id")
188
+ REFERENCES "workspace_artifact_versions"("workspace_id", "id")
189
+ DEFERRABLE INITIALLY DEFERRED;
190
+
191
+ CREATE TABLE "workspace_artifact_events" (
192
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
193
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
194
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
195
+ "artifact_id" uuid NOT NULL,
196
+ "type" text NOT NULL,
197
+ "from_version_id" uuid,
198
+ "to_version_id" uuid NOT NULL,
199
+ "operation_key" text NOT NULL,
200
+ "source_session_id" uuid,
201
+ "source_turn_id" uuid,
202
+ "source_attempt_id" uuid,
203
+ "source_execution_generation" integer,
204
+ "actor_subject_id" text NOT NULL,
205
+ "reason" text NOT NULL,
206
+ "created_at" timestamptz NOT NULL DEFAULT now(),
207
+ CONSTRAINT "workspace_artifact_events_artifact_fk"
208
+ FOREIGN KEY ("workspace_id", "artifact_id")
209
+ REFERENCES "workspace_artifacts"("workspace_id", "id") ON DELETE CASCADE,
210
+ CONSTRAINT "workspace_artifact_events_workspace_account_fk"
211
+ FOREIGN KEY ("workspace_id", "account_id")
212
+ REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
213
+ CONSTRAINT "workspace_artifact_events_operation_uq" UNIQUE ("workspace_id", "operation_key"),
214
+ CONSTRAINT "workspace_artifact_events_type_chk" CHECK ("type" IN ('published', 'rolled_back')),
215
+ CONSTRAINT "workspace_artifact_events_operation_chk" CHECK (length("operation_key") BETWEEN 1 AND 512),
216
+ CONSTRAINT "workspace_artifact_events_audit_chk" CHECK (
217
+ length(btrim("actor_subject_id")) BETWEEN 1 AND 1024
218
+ AND length(btrim("reason")) BETWEEN 1 AND 4096
219
+ ),
220
+ CONSTRAINT "workspace_artifact_events_provenance_chk" CHECK (
221
+ (
222
+ "source_session_id" IS NULL
223
+ AND "source_turn_id" IS NULL
224
+ AND "source_attempt_id" IS NULL
225
+ AND "source_execution_generation" IS NULL
226
+ ) OR (
227
+ "source_session_id" IS NOT NULL
228
+ AND "source_turn_id" IS NOT NULL
229
+ AND "source_attempt_id" IS NOT NULL
230
+ AND "source_execution_generation" > 0
231
+ )
232
+ )
233
+ );
234
+
235
+ CREATE INDEX "workspace_artifact_events_list_idx"
236
+ ON "workspace_artifact_events" ("workspace_id", "artifact_id", "created_at" DESC);
237
+
238
+ ALTER TABLE "workspace_artifact_versions"
239
+ ADD CONSTRAINT "workspace_artifact_versions_source_session_fk"
240
+ FOREIGN KEY ("workspace_id", "source_session_id")
241
+ REFERENCES "sessions"("workspace_id", "id") ON DELETE RESTRICT,
242
+ ADD CONSTRAINT "workspace_artifact_versions_source_turn_fk"
243
+ FOREIGN KEY ("workspace_id", "source_turn_id")
244
+ REFERENCES "session_turns"("workspace_id", "id") ON DELETE RESTRICT,
245
+ ADD CONSTRAINT "workspace_artifact_versions_source_attempt_fk"
246
+ FOREIGN KEY ("workspace_id", "source_attempt_id")
247
+ REFERENCES "session_turn_attempts"("workspace_id", "id") ON DELETE RESTRICT;
248
+
249
+ ALTER TABLE "workspace_artifact_events"
250
+ ADD CONSTRAINT "workspace_artifact_events_source_session_fk"
251
+ FOREIGN KEY ("workspace_id", "source_session_id")
252
+ REFERENCES "sessions"("workspace_id", "id") ON DELETE RESTRICT,
253
+ ADD CONSTRAINT "workspace_artifact_events_source_turn_fk"
254
+ FOREIGN KEY ("workspace_id", "source_turn_id")
255
+ REFERENCES "session_turns"("workspace_id", "id") ON DELETE RESTRICT,
256
+ ADD CONSTRAINT "workspace_artifact_events_source_attempt_fk"
257
+ FOREIGN KEY ("workspace_id", "source_attempt_id")
258
+ REFERENCES "session_turn_attempts"("workspace_id", "id") ON DELETE RESTRICT;
259
+
260
+ CREATE OR REPLACE FUNCTION workspace_artifact_validate_current()
261
+ RETURNS trigger LANGUAGE plpgsql AS $$
262
+ BEGIN
263
+ IF NEW."current_version_id" IS NOT NULL THEN
264
+ IF NOT EXISTS (
265
+ SELECT 1 FROM "workspace_artifact_versions" version
266
+ WHERE version."workspace_id" = NEW."workspace_id"
267
+ AND version."artifact_id" = NEW."id"
268
+ AND version."id" = NEW."current_version_id"
269
+ ) THEN
270
+ RAISE EXCEPTION 'artifact current version must belong to the artifact' USING ERRCODE = '23514';
271
+ END IF;
272
+ END IF;
273
+ RETURN NEW;
274
+ END;
275
+ $$;
276
+
277
+ CREATE OR REPLACE FUNCTION workspace_artifact_validate_event_links()
278
+ RETURNS trigger LANGUAGE plpgsql AS $$
279
+ BEGIN
280
+ IF NOT EXISTS (
281
+ SELECT 1 FROM "workspace_artifact_versions" version
282
+ WHERE version."workspace_id" = NEW."workspace_id"
283
+ AND version."artifact_id" = NEW."artifact_id"
284
+ AND version."id" = NEW."to_version_id"
285
+ ) OR (NEW."from_version_id" IS NOT NULL AND NOT EXISTS (
286
+ SELECT 1 FROM "workspace_artifact_versions" version
287
+ WHERE version."workspace_id" = NEW."workspace_id"
288
+ AND version."artifact_id" = NEW."artifact_id"
289
+ AND version."id" = NEW."from_version_id"
290
+ )) THEN
291
+ RAISE EXCEPTION 'artifact event versions must belong to the artifact' USING ERRCODE = '23514';
292
+ END IF;
293
+ RETURN NEW;
294
+ END;
295
+ $$;
296
+
297
+ CREATE CONSTRAINT TRIGGER workspace_artifacts_validate_current
298
+ AFTER INSERT OR UPDATE ON "workspace_artifacts"
299
+ DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION workspace_artifact_validate_current();
300
+ CREATE TRIGGER workspace_artifact_events_validate_links
301
+ BEFORE INSERT OR UPDATE ON "workspace_artifact_events"
302
+ FOR EACH ROW EXECUTE FUNCTION workspace_artifact_validate_event_links();
303
+
304
+ CREATE OR REPLACE FUNCTION workspace_artifact_reject_history_mutation()
305
+ RETURNS trigger LANGUAGE plpgsql AS $$
306
+ BEGIN
307
+ IF TG_OP = 'DELETE' AND pg_trigger_depth() > 1 AND NOT EXISTS (
308
+ SELECT 1 FROM "workspaces" workspace WHERE workspace."id" = OLD."workspace_id"
309
+ ) THEN
310
+ RETURN OLD;
311
+ END IF;
312
+ RAISE EXCEPTION 'workspace artifact history is immutable' USING ERRCODE = '55000';
313
+ END;
314
+ $$;
315
+
316
+ CREATE TRIGGER workspace_artifact_versions_immutable
317
+ BEFORE UPDATE OR DELETE ON "workspace_artifact_versions"
318
+ FOR EACH ROW EXECUTE FUNCTION workspace_artifact_reject_history_mutation();
319
+ CREATE TRIGGER workspace_artifact_events_immutable
320
+ BEFORE UPDATE OR DELETE ON "workspace_artifact_events"
321
+ FOR EACH ROW EXECUTE FUNCTION workspace_artifact_reject_history_mutation();
322
+
323
+ ALTER TABLE "workspace_artifacts" ENABLE ROW LEVEL SECURITY;
324
+ ALTER TABLE "workspace_artifacts" FORCE ROW LEVEL SECURITY;
325
+ ALTER TABLE "workspace_artifact_versions" ENABLE ROW LEVEL SECURITY;
326
+ ALTER TABLE "workspace_artifact_versions" FORCE ROW LEVEL SECURITY;
327
+ ALTER TABLE "workspace_artifact_events" ENABLE ROW LEVEL SECURITY;
328
+ ALTER TABLE "workspace_artifact_events" FORCE ROW LEVEL SECURITY;
329
+
330
+ CREATE POLICY workspace_isolation ON "workspace_artifacts"
331
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
332
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
333
+ CREATE POLICY workspace_isolation ON "workspace_artifact_versions"
334
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
335
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
336
+ CREATE POLICY workspace_isolation ON "workspace_artifact_events"
337
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
338
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
339
+
340
+ DO $$
341
+ BEGIN
342
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
343
+ REVOKE ALL PRIVILEGES ON TABLE "workspace_artifacts", "workspace_artifact_versions", "workspace_artifact_events" FROM opengeni_app;
344
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE "workspace_artifacts" TO opengeni_app;
345
+ GRANT SELECT, INSERT ON TABLE "workspace_artifact_versions", "workspace_artifact_events" TO opengeni_app;
346
+ END IF;
347
+ END;
348
+ $$;