@opengeni/db 0.28.1 → 0.31.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 (187) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +91 -0
  4. package/dist/browser-identities.d.ts +128 -0
  5. package/dist/browser-sessions.d.ts +272 -0
  6. package/dist/browser-state-artifacts.d.ts +17 -0
  7. package/dist/capability-components.d.ts +37 -0
  8. package/dist/capability-integrations.d.ts +192 -0
  9. package/dist/chunk-F5FT7BEZ.js +3535 -0
  10. package/dist/chunk-F5FT7BEZ.js.map +1 -0
  11. package/dist/chunk-ISI2TIUG.js +639 -0
  12. package/dist/chunk-ISI2TIUG.js.map +1 -0
  13. package/dist/chunk-MWDVXQOL.js +11639 -0
  14. package/dist/chunk-MWDVXQOL.js.map +1 -0
  15. package/dist/chunk-NBE6CHRI.js +955 -0
  16. package/dist/chunk-NBE6CHRI.js.map +1 -0
  17. package/dist/{chunk-DBS5HPEW.js → chunk-YHZXQ7HP.js} +760 -11
  18. package/dist/chunk-YHZXQ7HP.js.map +1 -0
  19. package/dist/codemode-operations.d.ts +117 -0
  20. package/dist/codex-token-resolver.d.ts +2 -2
  21. package/dist/company-profile-schema.d.ts +798 -0
  22. package/dist/company-profile.d.ts +268 -0
  23. package/dist/computer-sessions.d.ts +109 -0
  24. package/dist/connection-token-resolver.d.ts +9 -4
  25. package/dist/database.d.ts +52 -3
  26. package/dist/editable-artifact-durable-export.d.ts +153 -0
  27. package/dist/editable-artifact-durable-export.js +788 -0
  28. package/dist/editable-artifact-durable-export.js.map +1 -0
  29. package/dist/editable-artifact-materialization.d.ts +123 -0
  30. package/dist/editable-artifacts-schema.d.ts +4152 -0
  31. package/dist/editable-artifacts.d.ts +610 -0
  32. package/dist/editable-artifacts.js +26 -0
  33. package/dist/editable-artifacts.js.map +1 -0
  34. package/dist/environment-crypto.d.ts +8 -4
  35. package/dist/generated-images.d.ts +139 -0
  36. package/dist/index.d.ts +958 -126
  37. package/dist/index.js +31204 -12172
  38. package/dist/index.js.map +1 -1
  39. package/dist/insights.d.ts +51 -4
  40. package/dist/integration-bindings.d.ts +70 -0
  41. package/dist/integration-features.d.ts +99 -0
  42. package/dist/interaction-revisions.d.ts +3 -0
  43. package/dist/interaction-schema.d.ts +5372 -0
  44. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  45. package/dist/knowledge-source-sync.d.ts +274 -0
  46. package/dist/lossless-columns.d.ts +58 -0
  47. package/dist/lossless-json.d.ts +39 -0
  48. package/dist/memory-domain.d.ts +3 -6
  49. package/dist/memory-governance.d.ts +2 -2
  50. package/dist/memory-slack-delivery.d.ts +101 -0
  51. package/dist/new-session-drafts.d.ts +2 -2
  52. package/dist/pack-components.d.ts +76 -0
  53. package/dist/pack-installations.d.ts +86 -0
  54. package/dist/persistence-errors.d.ts +7 -14
  55. package/dist/plugin-packages.d.ts +126 -0
  56. package/dist/preference-registry.d.ts +1 -1
  57. package/dist/provision-roles.d.ts +9 -1
  58. package/dist/provision-roles.js +1 -1
  59. package/dist/runtime-posture.d.ts +18 -6
  60. package/dist/schema.d.ts +19122 -8731
  61. package/dist/schema.js +173 -1
  62. package/dist/scoped-knowledge.d.ts +97 -1
  63. package/dist/session-control.d.ts +4 -4
  64. package/dist/session-queue-commands.d.ts +19 -11
  65. package/dist/session-realtime-context.d.ts +2 -2
  66. package/dist/session-realtime-ledger.d.ts +4 -4
  67. package/dist/session-realtime-mirror.d.ts +2 -2
  68. package/dist/session-realtime-state.d.ts +1 -1
  69. package/dist/session-realtime-terminal.d.ts +8 -1
  70. package/dist/session-realtime.d.ts +7 -7
  71. package/dist/session-tool-call-settlement.d.ts +1 -1
  72. package/dist/slack-user-link-access.d.ts +54 -0
  73. package/dist/transcription-recordings-schema.d.ts +44 -6
  74. package/dist/transcription-recordings.d.ts +3 -3
  75. package/dist/turn-initiator.d.ts +2 -2
  76. package/dist/video-generation.d.ts +216 -0
  77. package/dist/video-generation.js +66 -0
  78. package/dist/video-generation.js.map +1 -0
  79. package/dist/workspace-artifacts.d.ts +1 -1
  80. package/dist/workspace-instruction-policies.d.ts +1 -1
  81. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  82. package/dist/workspace-learning-policy.d.ts +161 -0
  83. package/drizzle/0065_enrollment_credential_generation.sql +10 -0
  84. package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
  85. package/drizzle/0176_lossless_canonical_json.sql +975 -0
  86. package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
  87. package/drizzle/0178_permissioned_secret_reads.sql +14 -0
  88. package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
  89. package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
  90. package/drizzle/0181_connected_machine_removal.sql +52 -0
  91. package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
  92. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  93. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  94. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  95. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  96. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  97. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  98. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  99. package/drizzle/0190_timeline_annotations.sql +31 -0
  100. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  101. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  102. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  103. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  104. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  105. package/drizzle/0196_rig_provider_images.sql +152 -0
  106. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  107. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  108. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  109. package/drizzle/0201_company_profile_authority.sql +849 -0
  110. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  111. package/drizzle/0203_durable_video_generation.sql +353 -0
  112. package/drizzle/0204_video_generation_funding.sql +44 -0
  113. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  114. package/drizzle/0206_browser_sessions.sql +646 -0
  115. package/drizzle/0207_browser_identities.sql +379 -0
  116. package/drizzle/0208_attached_browser_devices.sql +165 -0
  117. package/drizzle/0209_computer_sessions.sql +575 -0
  118. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  119. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  120. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  121. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  122. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  123. package/drizzle/0215_capabilities_platform.sql +980 -0
  124. package/drizzle/0216_pack_component_ownership.sql +235 -0
  125. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  126. package/package.json +18 -5
  127. package/src/attached-browser-devices.ts +371 -0
  128. package/src/attempt-tool-catalogs.ts +131 -0
  129. package/src/browser-auth.ts +1896 -0
  130. package/src/browser-identities.ts +1168 -0
  131. package/src/browser-sessions.ts +2005 -0
  132. package/src/browser-state-artifacts.ts +59 -0
  133. package/src/capability-components.ts +166 -0
  134. package/src/capability-integrations.ts +1663 -0
  135. package/src/codemode-operations.ts +674 -0
  136. package/src/company-profile-schema.ts +143 -0
  137. package/src/company-profile.ts +968 -0
  138. package/src/computer-sessions.ts +1130 -0
  139. package/src/connection-token-resolver.ts +270 -25
  140. package/src/database.ts +434 -24
  141. package/src/editable-artifact-durable-export.ts +1201 -0
  142. package/src/editable-artifact-materialization.ts +893 -0
  143. package/src/editable-artifacts-schema.ts +879 -0
  144. package/src/editable-artifacts.ts +5404 -0
  145. package/src/environment-crypto.ts +22 -9
  146. package/src/generated-images.ts +740 -0
  147. package/src/index.ts +10117 -2730
  148. package/src/insights.ts +160 -13
  149. package/src/integration-bindings.ts +455 -0
  150. package/src/integration-features.ts +859 -0
  151. package/src/interaction-revisions.ts +40 -0
  152. package/src/interaction-schema.ts +1303 -0
  153. package/src/knowledge-source-sync-schema.ts +192 -0
  154. package/src/knowledge-source-sync.ts +1922 -0
  155. package/src/lossless-columns.ts +35 -0
  156. package/src/lossless-json.ts +322 -0
  157. package/src/memory-domain.ts +5 -44
  158. package/src/memory-slack-delivery.ts +801 -0
  159. package/src/pack-components.ts +954 -0
  160. package/src/pack-installations.ts +929 -0
  161. package/src/persistence-errors.ts +26 -34
  162. package/src/plugin-packages.ts +1186 -0
  163. package/src/provision-roles.ts +302 -1
  164. package/src/runtime-posture-cli.ts +9 -0
  165. package/src/runtime-posture.ts +514 -5
  166. package/src/schema.ts +5177 -2957
  167. package/src/scoped-knowledge-schema.ts +1 -1
  168. package/src/scoped-knowledge.ts +655 -3
  169. package/src/session-control.ts +133 -81
  170. package/src/session-queue-commands.ts +511 -247
  171. package/src/session-realtime-context.ts +20 -7
  172. package/src/session-realtime-ledger.ts +32 -10
  173. package/src/session-realtime-mirror.ts +4 -2
  174. package/src/session-realtime-terminal.ts +89 -21
  175. package/src/session-realtime.ts +10 -9
  176. package/src/session-tool-call-settlement.ts +38 -15
  177. package/src/slack-user-link-access.ts +702 -0
  178. package/src/transcription-recordings-schema.ts +5 -2
  179. package/src/transcription-recordings.ts +70 -40
  180. package/src/video-generation.ts +1452 -0
  181. package/src/workspace-learning-policy-schema.ts +139 -0
  182. package/src/workspace-learning-policy.ts +595 -0
  183. package/dist/chunk-DBS5HPEW.js.map +0 -1
  184. package/dist/chunk-HT5T62CC.js +0 -6803
  185. package/dist/chunk-HT5T62CC.js.map +0 -1
  186. package/dist/event-payload-sanitizer.d.ts +0 -32
  187. package/src/event-payload-sanitizer.ts +0 -377
@@ -0,0 +1,575 @@
1
+ -- deployment-mode: maintenance
2
+ -- Canonical durable ComputerSession registry. Native seat/display facts are
3
+ -- supplied only by the exact placement controller after physical activation.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '30s';
6
+
7
+ CREATE TABLE "computer_sessions" (
8
+ "id" uuid PRIMARY KEY,
9
+ "account_id" uuid NOT NULL,
10
+ "workspace_id" uuid NOT NULL,
11
+ "name" text NOT NULL,
12
+ "lifecycle" text NOT NULL DEFAULT 'starting',
13
+ "placement_kind" text NOT NULL,
14
+ "sandbox_group_id" uuid,
15
+ "connected_sandbox_id" uuid,
16
+ "device_id" uuid,
17
+ "external_provider_id" text,
18
+ "external_placement_id" text,
19
+ "controller_id" text,
20
+ "controller_generation" text,
21
+ "placement_instance_id" text,
22
+ "token_generation" integer NOT NULL DEFAULT 1,
23
+ "platform" text,
24
+ "adapter" text,
25
+ "seat_id" text,
26
+ "display_id" text,
27
+ "capabilities" jsonb,
28
+ "create_operation_id" uuid NOT NULL,
29
+ "created_by_subject_id" text NOT NULL,
30
+ "failure_code" text,
31
+ "controller_heartbeat_at" timestamptz,
32
+ "created_at" timestamptz NOT NULL DEFAULT now(),
33
+ "last_used_at" timestamptz NOT NULL DEFAULT now(),
34
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
35
+ CONSTRAINT "computer_sessions_workspace_account_fk"
36
+ FOREIGN KEY ("workspace_id", "account_id")
37
+ REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
38
+ CONSTRAINT "computer_sessions_create_operation_fk"
39
+ FOREIGN KEY ("workspace_id", "create_operation_id")
40
+ REFERENCES "interaction_operations"("workspace_id", "operation_id") ON DELETE RESTRICT,
41
+ CONSTRAINT "computer_sessions_lifecycle_check" CHECK (
42
+ "lifecycle" IN (
43
+ 'starting', 'active', 'suspending', 'suspended', 'restoring',
44
+ 'repair_required', 'lost', 'ending', 'ended', 'failed'
45
+ )
46
+ ),
47
+ CONSTRAINT "computer_sessions_platform_check"
48
+ CHECK ("platform" IS NULL OR "platform" IN ('linux', 'macos', 'windows')),
49
+ CONSTRAINT "computer_sessions_values_check" CHECK (
50
+ octet_length("name") BETWEEN 1 AND 200
51
+ AND "name" = btrim("name")
52
+ AND octet_length("created_by_subject_id") BETWEEN 1 AND 1024
53
+ AND "token_generation" > 0
54
+ AND ("adapter" IS NULL OR octet_length("adapter") BETWEEN 1 AND 512)
55
+ AND ("seat_id" IS NULL OR octet_length("seat_id") BETWEEN 1 AND 512)
56
+ AND ("display_id" IS NULL OR octet_length("display_id") BETWEEN 1 AND 512)
57
+ AND ("capabilities" IS NULL OR (
58
+ jsonb_typeof("capabilities") = 'object'
59
+ AND octet_length("capabilities"::text) BETWEEN 2 AND 65536
60
+ ))
61
+ AND ("failure_code" IS NULL OR octet_length("failure_code") BETWEEN 1 AND 512)
62
+ ),
63
+ CONSTRAINT "computer_sessions_placement_check" CHECK (
64
+ (
65
+ "placement_kind" = 'sandbox_group'
66
+ AND "sandbox_group_id" IS NOT NULL
67
+ AND "connected_sandbox_id" IS NULL
68
+ AND "device_id" IS NULL
69
+ AND "external_provider_id" IS NULL
70
+ AND "external_placement_id" IS NULL
71
+ ) OR (
72
+ "placement_kind" = 'connected_machine'
73
+ AND "sandbox_group_id" IS NULL
74
+ AND "connected_sandbox_id" IS NOT NULL
75
+ AND "device_id" IS NULL
76
+ AND "external_provider_id" IS NULL
77
+ AND "external_placement_id" IS NULL
78
+ ) OR (
79
+ "placement_kind" = 'attached_device'
80
+ AND "sandbox_group_id" IS NULL
81
+ AND "connected_sandbox_id" IS NULL
82
+ AND "device_id" IS NOT NULL
83
+ AND "external_provider_id" IS NULL
84
+ AND "external_placement_id" IS NULL
85
+ ) OR (
86
+ "placement_kind" = 'external_provider'
87
+ AND "sandbox_group_id" IS NULL
88
+ AND "connected_sandbox_id" IS NULL
89
+ AND "device_id" IS NULL
90
+ AND "external_provider_id" IS NOT NULL
91
+ AND "external_placement_id" IS NOT NULL
92
+ AND octet_length("external_provider_id") BETWEEN 1 AND 512
93
+ AND octet_length("external_placement_id") BETWEEN 1 AND 512
94
+ )
95
+ ),
96
+ CONSTRAINT "computer_sessions_controller_check" CHECK (
97
+ (
98
+ "controller_id" IS NULL
99
+ AND "controller_generation" IS NULL
100
+ AND "placement_instance_id" IS NULL
101
+ AND "controller_heartbeat_at" IS NULL
102
+ ) OR (
103
+ "controller_id" IS NOT NULL
104
+ AND octet_length("controller_id") BETWEEN 1 AND 512
105
+ AND "controller_generation" IS NOT NULL
106
+ AND octet_length("controller_generation") BETWEEN 1 AND 256
107
+ AND "placement_instance_id" IS NOT NULL
108
+ AND octet_length("placement_instance_id") BETWEEN 1 AND 512
109
+ AND "controller_heartbeat_at" IS NOT NULL
110
+ )
111
+ ),
112
+ CONSTRAINT "computer_sessions_native_binding_check" CHECK (
113
+ (
114
+ "platform" IS NULL
115
+ AND "adapter" IS NULL
116
+ AND "seat_id" IS NULL
117
+ AND "display_id" IS NULL
118
+ AND "capabilities" IS NULL
119
+ ) OR (
120
+ "platform" IS NOT NULL
121
+ AND "adapter" IS NOT NULL
122
+ AND "seat_id" IS NOT NULL
123
+ AND "display_id" IS NOT NULL
124
+ AND "capabilities" IS NOT NULL
125
+ )
126
+ ),
127
+ CONSTRAINT "computer_sessions_active_binding_check" CHECK (
128
+ "lifecycle" <> 'active' OR (
129
+ "controller_id" IS NOT NULL
130
+ AND "platform" IS NOT NULL
131
+ AND "capabilities" IS NOT NULL
132
+ )
133
+ ),
134
+ CONSTRAINT "computer_sessions_failure_check" CHECK (
135
+ (
136
+ "lifecycle" IN ('repair_required', 'lost', 'failed')
137
+ AND "failure_code" IS NOT NULL
138
+ ) OR (
139
+ "lifecycle" NOT IN ('repair_required', 'lost', 'failed')
140
+ AND "failure_code" IS NULL
141
+ )
142
+ ),
143
+ CONSTRAINT "computer_sessions_workspace_id_uq" UNIQUE ("workspace_id", "id"),
144
+ CONSTRAINT "computer_sessions_workspace_create_operation_uq"
145
+ UNIQUE ("workspace_id", "create_operation_id")
146
+ );
147
+
148
+ CREATE INDEX "computer_sessions_workspace_lifecycle_idx"
149
+ ON "computer_sessions" ("workspace_id", "lifecycle", "last_used_at", "id");
150
+ CREATE INDEX "computer_sessions_sandbox_group_idx"
151
+ ON "computer_sessions" ("workspace_id", "sandbox_group_id", "lifecycle");
152
+
153
+ CREATE TABLE "computer_session_associations" (
154
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
155
+ "account_id" uuid NOT NULL,
156
+ "workspace_id" uuid NOT NULL,
157
+ "computer_session_id" uuid NOT NULL,
158
+ "session_id" uuid NOT NULL,
159
+ "turn_id" uuid,
160
+ "attempt_id" uuid,
161
+ "relationship" text NOT NULL,
162
+ "actor_subject_id" text NOT NULL,
163
+ "last_used_at" timestamptz NOT NULL DEFAULT now(),
164
+ "created_at" timestamptz NOT NULL DEFAULT now(),
165
+ CONSTRAINT "computer_session_associations_workspace_account_fk"
166
+ FOREIGN KEY ("workspace_id", "account_id")
167
+ REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
168
+ CONSTRAINT "computer_session_associations_resource_fk"
169
+ FOREIGN KEY ("workspace_id", "computer_session_id")
170
+ REFERENCES "computer_sessions"("workspace_id", "id") ON DELETE CASCADE,
171
+ CONSTRAINT "computer_session_associations_session_fk"
172
+ FOREIGN KEY ("workspace_id", "session_id")
173
+ REFERENCES "sessions"("workspace_id", "id") ON DELETE CASCADE,
174
+ CONSTRAINT "computer_session_associations_turn_fk"
175
+ FOREIGN KEY ("workspace_id", "turn_id")
176
+ REFERENCES "session_turns"("workspace_id", "id") ON DELETE SET NULL ("turn_id"),
177
+ CONSTRAINT "computer_session_associations_attempt_fk"
178
+ FOREIGN KEY ("workspace_id", "attempt_id")
179
+ REFERENCES "session_turn_attempts"("workspace_id", "id") ON DELETE SET NULL ("attempt_id"),
180
+ CONSTRAINT "computer_session_associations_relationship_check"
181
+ CHECK ("relationship" IN ('created', 'using', 'observing', 'related')),
182
+ CONSTRAINT "computer_session_associations_values_check" CHECK (
183
+ octet_length("actor_subject_id") BETWEEN 1 AND 1024
184
+ AND ("attempt_id" IS NULL OR "turn_id" IS NOT NULL)
185
+ ),
186
+ CONSTRAINT "computer_session_associations_resource_session_relationship_uq"
187
+ UNIQUE ("computer_session_id", "session_id", "relationship")
188
+ );
189
+
190
+ CREATE INDEX "computer_session_associations_workspace_session_idx"
191
+ ON "computer_session_associations" ("workspace_id", "session_id", "last_used_at");
192
+ CREATE INDEX "computer_session_associations_resource_idx"
193
+ ON "computer_session_associations" ("workspace_id", "computer_session_id", "last_used_at");
194
+
195
+ ALTER TABLE "browser_sessions"
196
+ ADD CONSTRAINT "browser_sessions_linked_computer_session_fk"
197
+ FOREIGN KEY ("workspace_id", "linked_computer_session_id")
198
+ REFERENCES "computer_sessions"("workspace_id", "id")
199
+ ON DELETE SET NULL ("linked_computer_session_id");
200
+
201
+ ALTER TABLE "computer_sessions" ENABLE ROW LEVEL SECURITY;
202
+ ALTER TABLE "computer_sessions" FORCE ROW LEVEL SECURITY;
203
+ CREATE POLICY workspace_isolation ON "computer_sessions"
204
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
205
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
206
+
207
+ ALTER TABLE "computer_session_associations" ENABLE ROW LEVEL SECURITY;
208
+ ALTER TABLE "computer_session_associations" FORCE ROW LEVEL SECURITY;
209
+ CREATE POLICY workspace_isolation ON "computer_session_associations"
210
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
211
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
212
+
213
+ DO $grants$
214
+ DECLARE target_schema text := current_schema();
215
+ BEGIN
216
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
217
+ EXECUTE format(
218
+ 'REVOKE ALL ON TABLE %I.computer_sessions, %I.computer_session_associations FROM opengeni_app',
219
+ target_schema,
220
+ target_schema
221
+ );
222
+ EXECUTE format(
223
+ 'GRANT SELECT, INSERT, UPDATE ON TABLE %I.computer_sessions TO opengeni_app',
224
+ target_schema
225
+ );
226
+ EXECUTE format(
227
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.computer_session_associations TO opengeni_app',
228
+ target_schema
229
+ );
230
+ END IF;
231
+ END
232
+ $grants$;
233
+
234
+ -- Interaction holders have one shared liveness protocol. A holder remains live
235
+ -- only while its exact BrowserSession or ComputerSession is live on the same
236
+ -- sandbox-group lease. Stale controllers lose the durable resource before the
237
+ -- holder is removed and the placement becomes drainable.
238
+ DO $interaction_aware_reaper$
239
+ DECLARE data_schema text := current_schema();
240
+ BEGIN
241
+ EXECUTE format($create$
242
+ CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
243
+ p_viewer_holder_ttl_ms bigint,
244
+ p_turn_holder_ttl_ms bigint,
245
+ p_interaction_holder_ttl_ms bigint,
246
+ p_idle_grace_ms bigint
247
+ )
248
+ RETURNS TABLE (
249
+ workspace_id uuid,
250
+ sandbox_group_id uuid,
251
+ instance_id text,
252
+ lease_epoch integer
253
+ )
254
+ LANGUAGE plpgsql
255
+ SECURITY DEFINER
256
+ SET search_path = pg_catalog
257
+ AS $function$
258
+ DECLARE
259
+ locked_ids uuid[];
260
+ stale_interaction_ids uuid[];
261
+ changed_interaction_workspaces uuid[];
262
+ BEGIN
263
+ PERFORM pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2', '1', true);
264
+
265
+ SELECT coalesce(pg_catalog.array_agg(candidate.id), ARRAY[]::uuid[])
266
+ INTO locked_ids
267
+ FROM (
268
+ SELECT lease.id
269
+ FROM %1$I.sandbox_leases lease
270
+ WHERE lease.liveness <> 'cold'
271
+ OR EXISTS (
272
+ SELECT 1
273
+ FROM %1$I.sandbox_lease_holders holder
274
+ WHERE holder.lease_id = lease.id
275
+ )
276
+ ORDER BY lease.updated_at, lease.id
277
+ LIMIT 500
278
+ FOR UPDATE OF lease SKIP LOCKED
279
+ ) candidate;
280
+
281
+ IF pg_catalog.cardinality(locked_ids) = 0 THEN
282
+ RETURN;
283
+ END IF;
284
+
285
+ DELETE FROM %1$I.sandbox_lease_holders holder
286
+ USING (
287
+ SELECT stale.id
288
+ FROM %1$I.sandbox_lease_holders stale
289
+ WHERE stale.lease_id = ANY(locked_ids)
290
+ AND stale.kind IN ('viewer', 'direct')
291
+ AND stale.last_heartbeat_at
292
+ < pg_catalog.now()
293
+ - pg_catalog.make_interval(secs => p_viewer_holder_ttl_ms / 1000.0)
294
+ FOR UPDATE OF stale SKIP LOCKED
295
+ ) victim
296
+ WHERE holder.id = victim.id;
297
+
298
+ IF p_turn_holder_ttl_ms > 0 THEN
299
+ DELETE FROM %1$I.sandbox_lease_holders holder
300
+ USING (
301
+ SELECT stale.id
302
+ FROM %1$I.sandbox_lease_holders stale
303
+ WHERE stale.lease_id = ANY(locked_ids)
304
+ AND stale.kind = 'turn'
305
+ AND (
306
+ stale.last_heartbeat_at
307
+ < pg_catalog.now()
308
+ - pg_catalog.make_interval(secs => p_turn_holder_ttl_ms / 1000.0)
309
+ OR (
310
+ stale.holder_id ~* '^turn-attempt:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
311
+ AND NOT EXISTS (
312
+ SELECT 1
313
+ FROM %1$I.session_turn_attempts attempt
314
+ JOIN %1$I.session_turns turn
315
+ ON turn.account_id = attempt.account_id
316
+ AND turn.workspace_id = attempt.workspace_id
317
+ AND turn.session_id = attempt.session_id
318
+ AND turn.id = attempt.turn_id
319
+ AND turn.execution_generation = attempt.execution_generation
320
+ AND turn.active_attempt_id = attempt.id
321
+ WHERE stale.holder_id = ('turn-attempt:' || attempt.id::text)
322
+ AND attempt.account_id = stale.account_id
323
+ AND attempt.workspace_id = stale.workspace_id
324
+ AND attempt.session_id = stale.subject_id
325
+ AND attempt.state IN ('claimed', 'running')
326
+ )
327
+ )
328
+ )
329
+ FOR UPDATE OF stale SKIP LOCKED
330
+ ) victim
331
+ WHERE holder.id = victim.id;
332
+ END IF;
333
+
334
+ IF p_interaction_holder_ttl_ms > 0 THEN
335
+ SELECT coalesce(pg_catalog.array_agg(candidate.id), ARRAY[]::uuid[])
336
+ INTO stale_interaction_ids
337
+ FROM (
338
+ SELECT stale.id
339
+ FROM %1$I.sandbox_lease_holders stale
340
+ JOIN %1$I.sandbox_leases lease ON lease.id = stale.lease_id
341
+ WHERE stale.lease_id = ANY(locked_ids)
342
+ AND stale.kind = 'interaction'
343
+ AND (
344
+ stale.last_heartbeat_at
345
+ < pg_catalog.now()
346
+ - pg_catalog.make_interval(
347
+ secs => p_interaction_holder_ttl_ms / 1000.0
348
+ )
349
+ OR NOT (
350
+ EXISTS (
351
+ SELECT 1
352
+ FROM %1$I.browser_sessions browser
353
+ WHERE stale.holder_id = ('browser-session:' || browser.id::text)
354
+ AND browser.account_id = stale.account_id
355
+ AND browser.workspace_id = stale.workspace_id
356
+ AND browser.sandbox_group_id = lease.sandbox_group_id
357
+ AND browser.lifecycle IN (
358
+ 'starting', 'active', 'suspending', 'restoring', 'ending'
359
+ )
360
+ )
361
+ OR EXISTS (
362
+ SELECT 1
363
+ FROM %1$I.computer_sessions computer
364
+ WHERE stale.holder_id = ('computer-session:' || computer.id::text)
365
+ AND computer.account_id = stale.account_id
366
+ AND computer.workspace_id = stale.workspace_id
367
+ AND computer.sandbox_group_id = lease.sandbox_group_id
368
+ AND computer.lifecycle IN (
369
+ 'starting', 'active', 'suspending', 'restoring', 'ending'
370
+ )
371
+ )
372
+ )
373
+ )
374
+ FOR UPDATE OF stale SKIP LOCKED
375
+ ) candidate;
376
+
377
+ IF pg_catalog.cardinality(stale_interaction_ids) > 0 THEN
378
+ WITH updated_browser AS (
379
+ UPDATE %1$I.browser_sessions browser
380
+ SET lifecycle = 'lost',
381
+ controller_id = null,
382
+ controller_generation = null,
383
+ placement_instance_id = null,
384
+ controller_heartbeat_at = null,
385
+ failure_code = 'controller_heartbeat_expired',
386
+ updated_at = pg_catalog.now()
387
+ FROM %1$I.sandbox_lease_holders holder
388
+ WHERE holder.id = ANY(stale_interaction_ids)
389
+ AND holder.holder_id = ('browser-session:' || browser.id::text)
390
+ AND holder.account_id = browser.account_id
391
+ AND holder.workspace_id = browser.workspace_id
392
+ AND browser.lifecycle IN (
393
+ 'starting', 'active', 'suspending', 'restoring', 'ending'
394
+ )
395
+ RETURNING browser.workspace_id
396
+ ), updated_computer AS (
397
+ UPDATE %1$I.computer_sessions computer
398
+ SET lifecycle = 'lost',
399
+ controller_id = null,
400
+ controller_generation = null,
401
+ placement_instance_id = null,
402
+ controller_heartbeat_at = null,
403
+ failure_code = 'controller_heartbeat_expired',
404
+ updated_at = pg_catalog.now()
405
+ FROM %1$I.sandbox_lease_holders holder
406
+ WHERE holder.id = ANY(stale_interaction_ids)
407
+ AND holder.holder_id = ('computer-session:' || computer.id::text)
408
+ AND holder.account_id = computer.account_id
409
+ AND holder.workspace_id = computer.workspace_id
410
+ AND computer.lifecycle IN (
411
+ 'starting', 'active', 'suspending', 'restoring', 'ending'
412
+ )
413
+ RETURNING computer.workspace_id
414
+ ), updated AS (
415
+ SELECT workspace_id FROM updated_browser
416
+ UNION
417
+ SELECT workspace_id FROM updated_computer
418
+ )
419
+ SELECT coalesce(
420
+ pg_catalog.array_agg(DISTINCT updated.workspace_id),
421
+ ARRAY[]::uuid[]
422
+ )
423
+ INTO changed_interaction_workspaces
424
+ FROM updated;
425
+
426
+ DELETE FROM %1$I.sandbox_lease_holders holder
427
+ WHERE holder.id = ANY(stale_interaction_ids);
428
+
429
+ UPDATE %1$I.workspace_interaction_revisions revision
430
+ SET revision = revision.revision + 1,
431
+ updated_at = pg_catalog.now()
432
+ WHERE revision.workspace_id = ANY(changed_interaction_workspaces);
433
+ END IF;
434
+ END IF;
435
+
436
+ UPDATE %1$I.sandbox_leases lease SET
437
+ refcount = counts.total,
438
+ turn_holders = counts.turns,
439
+ viewer_holders = counts.viewers,
440
+ liveness = CASE
441
+ WHEN lease.liveness = 'warm'
442
+ AND counts.total = 0
443
+ AND counts.turns = 0
444
+ AND (
445
+ lease.reaper_hold_id IS NULL
446
+ OR lease.reaper_hold_until <= pg_catalog.now()
447
+ )
448
+ THEN 'draining' ELSE lease.liveness END,
449
+ expires_at = CASE
450
+ WHEN lease.liveness = 'warm'
451
+ AND counts.total = 0
452
+ AND counts.turns = 0
453
+ AND (
454
+ lease.reaper_hold_id IS NULL
455
+ OR lease.reaper_hold_until <= pg_catalog.now()
456
+ )
457
+ THEN CASE
458
+ WHEN lease.rotation_requested_at IS NOT NULL
459
+ OR lease.archive_capture_id IS NOT NULL
460
+ THEN pg_catalog.now() - interval '1 millisecond'
461
+ ELSE pg_catalog.now()
462
+ + pg_catalog.make_interval(secs => p_idle_grace_ms / 1000.0)
463
+ END
464
+ ELSE lease.expires_at END,
465
+ updated_at = pg_catalog.now()
466
+ FROM (
467
+ SELECT candidate.id,
468
+ pg_catalog.count(holder.id)::int AS total,
469
+ pg_catalog.count(holder.id)
470
+ FILTER (WHERE holder.kind = 'turn')::int AS turns,
471
+ pg_catalog.count(holder.id)
472
+ FILTER (WHERE holder.kind = 'viewer')::int AS viewers
473
+ FROM pg_catalog.unnest(locked_ids) candidate(id)
474
+ LEFT JOIN %1$I.sandbox_lease_holders holder
475
+ ON holder.lease_id = candidate.id
476
+ GROUP BY candidate.id
477
+ ) counts
478
+ WHERE lease.id = counts.id;
479
+
480
+ UPDATE %1$I.sandbox_leases lease SET
481
+ liveness = 'cold',
482
+ instance_id = null,
483
+ lease_epoch = lease.lease_epoch + 1,
484
+ reaper_hold_id = null,
485
+ reaper_hold_until = null,
486
+ reaper_hold_reason = null,
487
+ resume_state = opengeni_private.warming_reset_resume_state_v2(
488
+ lease.backend,
489
+ lease.resume_backend_id,
490
+ lease.resume_state,
491
+ lease.workspace_generation,
492
+ lease.archive_generation,
493
+ pg_catalog.clock_timestamp()
494
+ ),
495
+ resume_backend_id = CASE
496
+ WHEN coalesce(
497
+ lease.resume_state #>> '{sessionState,workspaceArchive}', ''
498
+ ) <> ''
499
+ OR coalesce(
500
+ lease.resume_state #>> '{sessionState,workspaceArchivePrev}', ''
501
+ ) <> ''
502
+ THEN coalesce(lease.resume_backend_id, lease.backend)
503
+ ELSE null END,
504
+ data_plane_url = null,
505
+ terminal_data_plane_url = null,
506
+ provider_created_at = null,
507
+ provider_deadline_at = null,
508
+ rotation_requested_at = null,
509
+ rotation_reason = null,
510
+ updated_at = pg_catalog.now()
511
+ WHERE lease.id = ANY(locked_ids)
512
+ AND lease.liveness = 'warming'
513
+ AND lease.expires_at < pg_catalog.now()
514
+ AND lease.instance_id IS NULL
515
+ AND (
516
+ lease.reaper_hold_id IS NULL
517
+ OR lease.reaper_hold_until <= pg_catalog.now()
518
+ );
519
+
520
+ UPDATE %1$I.sandbox_leases lease SET
521
+ liveness = 'draining',
522
+ refcount = 0,
523
+ turn_holders = 0,
524
+ viewer_holders = 0,
525
+ data_plane_url = null,
526
+ terminal_data_plane_url = null,
527
+ lease_epoch = lease.lease_epoch + 1,
528
+ reaper_hold_id = null,
529
+ reaper_hold_until = null,
530
+ reaper_hold_reason = null,
531
+ expires_at = pg_catalog.now() - interval '1 millisecond',
532
+ updated_at = pg_catalog.now()
533
+ WHERE lease.id = ANY(locked_ids)
534
+ AND lease.liveness = 'warming'
535
+ AND lease.expires_at < pg_catalog.now()
536
+ AND lease.instance_id IS NOT NULL
537
+ AND (
538
+ lease.reaper_hold_id IS NULL
539
+ OR lease.reaper_hold_until <= pg_catalog.now()
540
+ );
541
+
542
+ RETURN QUERY
543
+ SELECT lease.workspace_id, lease.sandbox_group_id,
544
+ lease.instance_id, lease.lease_epoch
545
+ FROM %1$I.sandbox_leases lease
546
+ WHERE lease.id = ANY(locked_ids)
547
+ AND lease.liveness = 'draining'
548
+ AND lease.expires_at < pg_catalog.now()
549
+ AND lease.refcount = 0
550
+ AND (
551
+ lease.reaper_hold_id IS NULL
552
+ OR lease.reaper_hold_until <= pg_catalog.now()
553
+ );
554
+ END;
555
+ $function$;
556
+ $create$, data_schema);
557
+ END
558
+ $interaction_aware_reaper$;
559
+
560
+ REVOKE ALL ON FUNCTION opengeni_private.reap_sandbox_leases(
561
+ bigint, bigint, bigint, bigint
562
+ ) FROM PUBLIC;
563
+
564
+ DO $reaper_grant$
565
+ BEGIN
566
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
567
+ GRANT EXECUTE ON FUNCTION opengeni_private.reap_sandbox_leases(
568
+ bigint, bigint, bigint, bigint
569
+ ) TO opengeni_app;
570
+ END IF;
571
+ END
572
+ $reaper_grant$;
573
+
574
+ RESET statement_timeout;
575
+ RESET lock_timeout;