@opengeni/db 0.31.1 → 0.36.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.
Files changed (122) hide show
  1. package/dist/browser-auth.d.ts +209 -1
  2. package/dist/browser-downloads.d.ts +51 -0
  3. package/dist/browser-identities.d.ts +11 -2
  4. package/dist/browser-sessions.d.ts +83 -1
  5. package/dist/browser-state-artifacts.d.ts +75 -1
  6. package/dist/canonical-human-identities.d.ts +48 -0
  7. package/dist/canonical-human-identities.js +28 -0
  8. package/dist/canonical-human-identities.js.map +1 -0
  9. package/dist/capability-integrations.d.ts +8 -6
  10. package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
  11. package/dist/chunk-5KTIEDX7.js.map +1 -0
  12. package/dist/{chunk-NBE6CHRI.js → chunk-CQZ4QSP5.js} +49 -9
  13. package/dist/chunk-CQZ4QSP5.js.map +1 -0
  14. package/dist/chunk-F32YT7MP.js +117 -0
  15. package/dist/chunk-F32YT7MP.js.map +1 -0
  16. package/dist/{chunk-ISI2TIUG.js → chunk-JC5QPLUM.js} +30 -3
  17. package/dist/chunk-JC5QPLUM.js.map +1 -0
  18. package/dist/{chunk-F5FT7BEZ.js → chunk-P3RE4D2B.js} +2 -2
  19. package/dist/chunk-TAUTWOS5.js +259 -0
  20. package/dist/chunk-TAUTWOS5.js.map +1 -0
  21. package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
  22. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  23. package/dist/database.d.ts +5 -0
  24. package/dist/editable-artifact-durable-export.js +2 -2
  25. package/dist/editable-artifacts.js +3 -3
  26. package/dist/index.d.ts +438 -29
  27. package/dist/index.js +35640 -28969
  28. package/dist/index.js.map +1 -1
  29. package/dist/insights.d.ts +23 -7
  30. package/dist/integration-bindings.d.ts +24 -24
  31. package/dist/integration-facets.d.ts +99 -0
  32. package/dist/interaction-revisions.d.ts +11 -0
  33. package/dist/interaction-schema.d.ts +698 -34
  34. package/dist/provision-roles.js +1 -1
  35. package/dist/runtime-posture.d.ts +8 -7
  36. package/dist/schema.d.ts +9464 -5412
  37. package/dist/schema.js +51 -9
  38. package/dist/session-control.d.ts +29 -3
  39. package/dist/session-tenancy.d.ts +44 -0
  40. package/dist/session-tenancy.js +16 -0
  41. package/dist/session-tenancy.js.map +1 -0
  42. package/dist/slack-task-policy-schema.d.ts +847 -0
  43. package/dist/slack-task-policy.d.ts +65 -0
  44. package/dist/task-notes-schema.d.ts +924 -0
  45. package/dist/task-notes.d.ts +85 -0
  46. package/dist/video-generation.d.ts +9 -0
  47. package/dist/video-generation.js +5 -3
  48. package/dist/xai-subscription.d.ts +326 -0
  49. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  50. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  51. package/drizzle/0214_browser_download_saves.sql +34 -0
  52. package/drizzle/0215_browser_controller_host.sql +385 -0
  53. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  54. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  55. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  56. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  57. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  58. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  59. package/drizzle/0220_session_channels.sql +54 -0
  60. package/drizzle/0221_sessions_channel_index.sql +5 -0
  61. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  62. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  63. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  64. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  65. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  66. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  67. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  68. package/drizzle/0227_slack_native_actions.sql +145 -0
  69. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  70. package/drizzle/0228_slack_task_policy.sql +400 -0
  71. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  72. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  73. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  74. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  75. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  76. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  77. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  78. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  79. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  80. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  81. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  82. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  83. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  84. package/drizzle/0239_task_tree_notes.sql +990 -0
  85. package/package.json +13 -5
  86. package/src/browser-auth.ts +2600 -123
  87. package/src/browser-downloads.ts +517 -0
  88. package/src/browser-identities.ts +220 -3
  89. package/src/browser-sessions.ts +334 -45
  90. package/src/browser-state-artifacts.ts +402 -12
  91. package/src/canonical-human-identities.ts +317 -0
  92. package/src/capability-integrations.ts +121 -213
  93. package/src/database.ts +41 -1
  94. package/src/index.ts +4157 -394
  95. package/src/insights.ts +109 -28
  96. package/src/integration-bindings.ts +101 -101
  97. package/src/{integration-features.ts → integration-facets.ts} +187 -184
  98. package/src/interaction-revisions.ts +33 -3
  99. package/src/interaction-schema.ts +311 -8
  100. package/src/pack-components.ts +43 -43
  101. package/src/pack-installations.ts +0 -43
  102. package/src/plugin-packages.ts +9 -9
  103. package/src/provision-roles.ts +232 -0
  104. package/src/runtime-posture.ts +227 -14
  105. package/src/schema.ts +1188 -75
  106. package/src/session-control.ts +122 -37
  107. package/src/session-queue-commands.ts +78 -1
  108. package/src/session-tenancy.ts +188 -0
  109. package/src/session-tool-call-settlement.ts +9 -3
  110. package/src/slack-task-policy-schema.ts +130 -0
  111. package/src/slack-task-policy.ts +341 -0
  112. package/src/task-notes-schema.ts +135 -0
  113. package/src/task-notes.ts +178 -0
  114. package/src/video-generation.ts +90 -12
  115. package/src/workspace-artifacts.ts +1 -1
  116. package/src/xai-subscription.ts +1887 -0
  117. package/dist/chunk-ISI2TIUG.js.map +0 -1
  118. package/dist/chunk-MWDVXQOL.js.map +0 -1
  119. package/dist/chunk-NBE6CHRI.js.map +0 -1
  120. package/dist/chunk-YHZXQ7HP.js.map +0 -1
  121. package/dist/integration-features.d.ts +0 -99
  122. /package/dist/{chunk-F5FT7BEZ.js.map → chunk-P3RE4D2B.js.map} +0 -0
@@ -0,0 +1,385 @@
1
+ -- deployment-mode: maintenance
2
+ -- Separate a remote browser's logical placement from the sandbox that hosts
3
+ -- its sole browserd mutation controller. Existing managed sessions preserve
4
+ -- their current home placement; remote rows are backfilled from their immutable
5
+ -- creation association.
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '30s';
8
+
9
+ ALTER TABLE "browser_sessions"
10
+ ADD COLUMN "controller_host_sandbox_group_id" uuid;
11
+
12
+ UPDATE "browser_sessions"
13
+ SET "controller_host_sandbox_group_id" = "sandbox_group_id"
14
+ WHERE "placement_kind" = 'sandbox_group';
15
+
16
+ UPDATE "browser_sessions" browser
17
+ SET "controller_host_sandbox_group_id" = source."sandbox_group_id"
18
+ FROM "browser_session_associations" association
19
+ JOIN "sessions" source
20
+ ON source."workspace_id" = association."workspace_id"
21
+ AND source."id" = association."session_id"
22
+ WHERE browser."workspace_id" = association."workspace_id"
23
+ AND browser."id" = association."browser_session_id"
24
+ AND browser."placement_kind" = 'external_provider'
25
+ AND association."relationship" = 'created';
26
+
27
+ ALTER TABLE "browser_sessions"
28
+ ADD CONSTRAINT "browser_sessions_controller_host_check" CHECK (
29
+ ("placement_kind" = 'sandbox_group'
30
+ AND "controller_host_sandbox_group_id" = "sandbox_group_id")
31
+ OR
32
+ ("placement_kind" = 'external_provider'
33
+ AND "controller_host_sandbox_group_id" IS NOT NULL)
34
+ OR
35
+ ("placement_kind" IN ('connected_machine', 'attached_device')
36
+ AND "controller_host_sandbox_group_id" IS NULL)
37
+ );
38
+
39
+ CREATE INDEX "browser_sessions_controller_host_sandbox_group_idx"
40
+ ON "browser_sessions"
41
+ ("workspace_id", "controller_host_sandbox_group_id", "lifecycle");
42
+
43
+ -- Reinstall the interaction-aware lease reaper against controller locality,
44
+ -- not logical browser locality. This keeps a remote browser alive exactly while
45
+ -- its home browserd lease and holder remain authoritative.
46
+ DO $interaction_aware_reaper$
47
+ DECLARE data_schema text := current_schema();
48
+ BEGIN
49
+ EXECUTE format($create$
50
+ CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
51
+ p_viewer_holder_ttl_ms bigint,
52
+ p_turn_holder_ttl_ms bigint,
53
+ p_interaction_holder_ttl_ms bigint,
54
+ p_idle_grace_ms bigint
55
+ )
56
+ RETURNS TABLE (
57
+ workspace_id uuid,
58
+ sandbox_group_id uuid,
59
+ instance_id text,
60
+ lease_epoch integer
61
+ )
62
+ LANGUAGE plpgsql
63
+ SECURITY DEFINER
64
+ SET search_path = pg_catalog
65
+ AS $function$
66
+ DECLARE
67
+ locked_ids uuid[];
68
+ stale_interaction_ids uuid[];
69
+ changed_interaction_workspaces uuid[];
70
+ BEGIN
71
+ PERFORM pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2', '1', true);
72
+
73
+ SELECT coalesce(pg_catalog.array_agg(candidate.id), ARRAY[]::uuid[])
74
+ INTO locked_ids
75
+ FROM (
76
+ SELECT lease.id
77
+ FROM %1$I.sandbox_leases lease
78
+ WHERE lease.liveness <> 'cold'
79
+ OR EXISTS (
80
+ SELECT 1
81
+ FROM %1$I.sandbox_lease_holders holder
82
+ WHERE holder.lease_id = lease.id
83
+ )
84
+ ORDER BY lease.updated_at, lease.id
85
+ LIMIT 500
86
+ FOR UPDATE OF lease SKIP LOCKED
87
+ ) candidate;
88
+
89
+ IF pg_catalog.cardinality(locked_ids) = 0 THEN
90
+ RETURN;
91
+ END IF;
92
+
93
+ DELETE FROM %1$I.sandbox_lease_holders holder
94
+ USING (
95
+ SELECT stale.id
96
+ FROM %1$I.sandbox_lease_holders stale
97
+ WHERE stale.lease_id = ANY(locked_ids)
98
+ AND stale.kind IN ('viewer', 'direct')
99
+ AND stale.last_heartbeat_at
100
+ < pg_catalog.now()
101
+ - pg_catalog.make_interval(secs => p_viewer_holder_ttl_ms / 1000.0)
102
+ FOR UPDATE OF stale SKIP LOCKED
103
+ ) victim
104
+ WHERE holder.id = victim.id;
105
+
106
+ IF p_turn_holder_ttl_ms > 0 THEN
107
+ DELETE FROM %1$I.sandbox_lease_holders holder
108
+ USING (
109
+ SELECT stale.id
110
+ FROM %1$I.sandbox_lease_holders stale
111
+ WHERE stale.lease_id = ANY(locked_ids)
112
+ AND stale.kind = 'turn'
113
+ AND (
114
+ stale.last_heartbeat_at
115
+ < pg_catalog.now()
116
+ - pg_catalog.make_interval(secs => p_turn_holder_ttl_ms / 1000.0)
117
+ OR (
118
+ 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}$'
119
+ AND NOT EXISTS (
120
+ SELECT 1
121
+ FROM %1$I.session_turn_attempts attempt
122
+ JOIN %1$I.session_turns turn
123
+ ON turn.account_id = attempt.account_id
124
+ AND turn.workspace_id = attempt.workspace_id
125
+ AND turn.session_id = attempt.session_id
126
+ AND turn.id = attempt.turn_id
127
+ AND turn.execution_generation = attempt.execution_generation
128
+ AND turn.active_attempt_id = attempt.id
129
+ WHERE stale.holder_id = ('turn-attempt:' || attempt.id::text)
130
+ AND attempt.account_id = stale.account_id
131
+ AND attempt.workspace_id = stale.workspace_id
132
+ AND attempt.session_id = stale.subject_id
133
+ AND attempt.state IN ('claimed', 'running')
134
+ )
135
+ )
136
+ )
137
+ FOR UPDATE OF stale SKIP LOCKED
138
+ ) victim
139
+ WHERE holder.id = victim.id;
140
+ END IF;
141
+
142
+ IF p_interaction_holder_ttl_ms > 0 THEN
143
+ SELECT coalesce(pg_catalog.array_agg(candidate.id), ARRAY[]::uuid[])
144
+ INTO stale_interaction_ids
145
+ FROM (
146
+ SELECT stale.id
147
+ FROM %1$I.sandbox_lease_holders stale
148
+ JOIN %1$I.sandbox_leases lease ON lease.id = stale.lease_id
149
+ WHERE stale.lease_id = ANY(locked_ids)
150
+ AND stale.kind = 'interaction'
151
+ AND (
152
+ stale.last_heartbeat_at
153
+ < pg_catalog.now()
154
+ - pg_catalog.make_interval(
155
+ secs => p_interaction_holder_ttl_ms / 1000.0
156
+ )
157
+ OR NOT (
158
+ EXISTS (
159
+ SELECT 1
160
+ FROM %1$I.browser_sessions browser
161
+ WHERE stale.holder_id = ('browser-session:' || browser.id::text)
162
+ AND browser.account_id = stale.account_id
163
+ AND browser.workspace_id = stale.workspace_id
164
+ AND browser.controller_host_sandbox_group_id = lease.sandbox_group_id
165
+ AND browser.lifecycle IN (
166
+ 'starting', 'active', 'suspending', 'restoring', 'ending'
167
+ )
168
+ )
169
+ OR EXISTS (
170
+ SELECT 1
171
+ FROM %1$I.computer_sessions computer
172
+ WHERE stale.holder_id = ('computer-session:' || computer.id::text)
173
+ AND computer.account_id = stale.account_id
174
+ AND computer.workspace_id = stale.workspace_id
175
+ AND computer.sandbox_group_id = lease.sandbox_group_id
176
+ AND computer.lifecycle IN (
177
+ 'starting', 'active', 'suspending', 'restoring', 'ending'
178
+ )
179
+ )
180
+ )
181
+ )
182
+ FOR UPDATE OF stale SKIP LOCKED
183
+ ) candidate;
184
+
185
+ IF pg_catalog.cardinality(stale_interaction_ids) > 0 THEN
186
+ WITH updated_browser AS (
187
+ UPDATE %1$I.browser_sessions browser
188
+ SET lifecycle = 'lost',
189
+ controller_id = null,
190
+ controller_generation = null,
191
+ placement_instance_id = null,
192
+ controller_heartbeat_at = null,
193
+ failure_code = 'controller_heartbeat_expired',
194
+ updated_at = pg_catalog.now()
195
+ FROM %1$I.sandbox_lease_holders holder
196
+ WHERE holder.id = ANY(stale_interaction_ids)
197
+ AND holder.holder_id = ('browser-session:' || browser.id::text)
198
+ AND holder.account_id = browser.account_id
199
+ AND holder.workspace_id = browser.workspace_id
200
+ AND browser.lifecycle IN (
201
+ 'starting', 'active', 'suspending', 'restoring', 'ending'
202
+ )
203
+ RETURNING browser.workspace_id
204
+ ), updated_computer AS (
205
+ UPDATE %1$I.computer_sessions computer
206
+ SET lifecycle = 'lost',
207
+ controller_id = null,
208
+ controller_generation = null,
209
+ placement_instance_id = null,
210
+ controller_heartbeat_at = null,
211
+ failure_code = 'controller_heartbeat_expired',
212
+ updated_at = pg_catalog.now()
213
+ FROM %1$I.sandbox_lease_holders holder
214
+ WHERE holder.id = ANY(stale_interaction_ids)
215
+ AND holder.holder_id = ('computer-session:' || computer.id::text)
216
+ AND holder.account_id = computer.account_id
217
+ AND holder.workspace_id = computer.workspace_id
218
+ AND computer.lifecycle IN (
219
+ 'starting', 'active', 'suspending', 'restoring', 'ending'
220
+ )
221
+ RETURNING computer.workspace_id
222
+ ), updated AS (
223
+ SELECT updated_browser.workspace_id AS interaction_workspace_id
224
+ FROM updated_browser
225
+ UNION
226
+ SELECT updated_computer.workspace_id AS interaction_workspace_id
227
+ FROM updated_computer
228
+ )
229
+ SELECT coalesce(
230
+ pg_catalog.array_agg(DISTINCT updated.interaction_workspace_id),
231
+ ARRAY[]::uuid[]
232
+ )
233
+ INTO changed_interaction_workspaces
234
+ FROM updated;
235
+
236
+ DELETE FROM %1$I.sandbox_lease_holders holder
237
+ WHERE holder.id = ANY(stale_interaction_ids);
238
+
239
+ UPDATE %1$I.workspace_interaction_revisions revision
240
+ SET revision = revision.revision + 1,
241
+ updated_at = pg_catalog.now()
242
+ WHERE revision.workspace_id = ANY(changed_interaction_workspaces);
243
+ END IF;
244
+ END IF;
245
+
246
+ UPDATE %1$I.sandbox_leases lease SET
247
+ refcount = counts.total,
248
+ turn_holders = counts.turns,
249
+ viewer_holders = counts.viewers,
250
+ liveness = CASE
251
+ WHEN lease.liveness = 'warm'
252
+ AND counts.total = 0
253
+ AND counts.turns = 0
254
+ AND (
255
+ lease.reaper_hold_id IS NULL
256
+ OR lease.reaper_hold_until <= pg_catalog.now()
257
+ )
258
+ THEN 'draining' ELSE lease.liveness END,
259
+ expires_at = CASE
260
+ WHEN lease.liveness = 'warm'
261
+ AND counts.total = 0
262
+ AND counts.turns = 0
263
+ AND (
264
+ lease.reaper_hold_id IS NULL
265
+ OR lease.reaper_hold_until <= pg_catalog.now()
266
+ )
267
+ THEN CASE
268
+ WHEN lease.rotation_requested_at IS NOT NULL
269
+ OR lease.archive_capture_id IS NOT NULL
270
+ THEN pg_catalog.now() - interval '1 millisecond'
271
+ ELSE pg_catalog.now()
272
+ + pg_catalog.make_interval(secs => p_idle_grace_ms / 1000.0)
273
+ END
274
+ ELSE lease.expires_at END,
275
+ updated_at = pg_catalog.now()
276
+ FROM (
277
+ SELECT candidate.id,
278
+ pg_catalog.count(holder.id)::int AS total,
279
+ pg_catalog.count(holder.id)
280
+ FILTER (WHERE holder.kind = 'turn')::int AS turns,
281
+ pg_catalog.count(holder.id)
282
+ FILTER (WHERE holder.kind = 'viewer')::int AS viewers
283
+ FROM pg_catalog.unnest(locked_ids) candidate(id)
284
+ LEFT JOIN %1$I.sandbox_lease_holders holder
285
+ ON holder.lease_id = candidate.id
286
+ GROUP BY candidate.id
287
+ ) counts
288
+ WHERE lease.id = counts.id;
289
+
290
+ UPDATE %1$I.sandbox_leases lease SET
291
+ liveness = 'cold',
292
+ instance_id = null,
293
+ lease_epoch = lease.lease_epoch + 1,
294
+ reaper_hold_id = null,
295
+ reaper_hold_until = null,
296
+ reaper_hold_reason = null,
297
+ resume_state = opengeni_private.warming_reset_resume_state_v2(
298
+ lease.backend,
299
+ lease.resume_backend_id,
300
+ lease.resume_state,
301
+ lease.workspace_generation,
302
+ lease.archive_generation,
303
+ pg_catalog.clock_timestamp()
304
+ ),
305
+ resume_backend_id = CASE
306
+ WHEN coalesce(
307
+ lease.resume_state #>> '{sessionState,workspaceArchive}', ''
308
+ ) <> ''
309
+ OR coalesce(
310
+ lease.resume_state #>> '{sessionState,workspaceArchivePrev}', ''
311
+ ) <> ''
312
+ THEN coalesce(lease.resume_backend_id, lease.backend)
313
+ ELSE null END,
314
+ data_plane_url = null,
315
+ terminal_data_plane_url = null,
316
+ provider_created_at = null,
317
+ provider_deadline_at = null,
318
+ rotation_requested_at = null,
319
+ rotation_reason = null,
320
+ updated_at = pg_catalog.now()
321
+ WHERE lease.id = ANY(locked_ids)
322
+ AND lease.liveness = 'warming'
323
+ AND lease.expires_at < pg_catalog.now()
324
+ AND lease.instance_id IS NULL
325
+ AND (
326
+ lease.reaper_hold_id IS NULL
327
+ OR lease.reaper_hold_until <= pg_catalog.now()
328
+ );
329
+
330
+ UPDATE %1$I.sandbox_leases lease SET
331
+ liveness = 'draining',
332
+ refcount = 0,
333
+ turn_holders = 0,
334
+ viewer_holders = 0,
335
+ data_plane_url = null,
336
+ terminal_data_plane_url = null,
337
+ lease_epoch = lease.lease_epoch + 1,
338
+ reaper_hold_id = null,
339
+ reaper_hold_until = null,
340
+ reaper_hold_reason = null,
341
+ expires_at = pg_catalog.now() - interval '1 millisecond',
342
+ updated_at = pg_catalog.now()
343
+ WHERE lease.id = ANY(locked_ids)
344
+ AND lease.liveness = 'warming'
345
+ AND lease.expires_at < pg_catalog.now()
346
+ AND lease.instance_id IS NOT NULL
347
+ AND (
348
+ lease.reaper_hold_id IS NULL
349
+ OR lease.reaper_hold_until <= pg_catalog.now()
350
+ );
351
+
352
+ RETURN QUERY
353
+ SELECT lease.workspace_id, lease.sandbox_group_id,
354
+ lease.instance_id, lease.lease_epoch
355
+ FROM %1$I.sandbox_leases lease
356
+ WHERE lease.id = ANY(locked_ids)
357
+ AND lease.liveness = 'draining'
358
+ AND lease.expires_at < pg_catalog.now()
359
+ AND lease.refcount = 0
360
+ AND (
361
+ lease.reaper_hold_id IS NULL
362
+ OR lease.reaper_hold_until <= pg_catalog.now()
363
+ );
364
+ END;
365
+ $function$;
366
+ $create$, data_schema);
367
+ END
368
+ $interaction_aware_reaper$;
369
+
370
+ REVOKE ALL ON FUNCTION opengeni_private.reap_sandbox_leases(
371
+ bigint, bigint, bigint, bigint
372
+ ) FROM PUBLIC;
373
+
374
+ DO $reaper_grant$
375
+ BEGIN
376
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
377
+ GRANT EXECUTE ON FUNCTION opengeni_private.reap_sandbox_leases(
378
+ bigint, bigint, bigint, bigint
379
+ ) TO opengeni_app;
380
+ END IF;
381
+ END
382
+ $reaper_grant$;
383
+
384
+ RESET statement_timeout;
385
+ RESET lock_timeout;
@@ -0,0 +1,137 @@
1
+ -- deployment-mode: maintenance
2
+ -- Causally ordered SiteAuth health evidence. Browser runs remain independent;
3
+ -- only the newest-started terminal evidence may change the shared projection.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '30s';
6
+
7
+ CREATE SEQUENCE "auth_runs_health_sequence_seq" AS bigint;
8
+
9
+ ALTER TABLE "auth_runs"
10
+ ADD COLUMN "purpose" text NOT NULL DEFAULT 'authenticate',
11
+ ADD COLUMN "health_sequence" bigint NOT NULL
12
+ DEFAULT nextval('auth_runs_health_sequence_seq'::regclass);
13
+
14
+ ALTER SEQUENCE "auth_runs_health_sequence_seq"
15
+ OWNED BY "auth_runs"."health_sequence";
16
+
17
+ -- The volatile default gives legacy rows values, but physical row order is not
18
+ -- causal. Re-rank the bounded pre-cutover history before admitting writers.
19
+ WITH ordered AS (
20
+ SELECT
21
+ "id",
22
+ row_number() OVER (ORDER BY "created_at", "id")::bigint AS "health_sequence"
23
+ FROM "auth_runs"
24
+ )
25
+ UPDATE "auth_runs" AS run
26
+ SET "health_sequence" = ordered."health_sequence"
27
+ FROM ordered
28
+ WHERE run."id" = ordered."id";
29
+
30
+ SELECT setval(
31
+ 'auth_runs_health_sequence_seq'::regclass,
32
+ greatest(coalesce((SELECT max("health_sequence") FROM "auth_runs"), 0), 1),
33
+ coalesce((SELECT max("health_sequence") FROM "auth_runs"), 0) > 0
34
+ );
35
+
36
+ ALTER TABLE "auth_runs"
37
+ ADD CONSTRAINT "auth_runs_health_evidence_check" CHECK (
38
+ "purpose" IN ('authenticate', 'health_check', 'repair')
39
+ AND "health_sequence" > 0
40
+ );
41
+
42
+ ALTER TABLE "site_auth_connections"
43
+ ADD COLUMN "last_checked_at" timestamptz,
44
+ ADD COLUMN "next_check_at" timestamptz,
45
+ ADD COLUMN "health_sequence" bigint NOT NULL DEFAULT 0;
46
+
47
+ -- Existing successful verification is preserved. Existing terminal failures,
48
+ -- which the old writer failed to project, become repair evidence now.
49
+ WITH latest AS (
50
+ SELECT DISTINCT ON (run."site_auth_connection_id")
51
+ run."site_auth_connection_id",
52
+ run."id",
53
+ run."state",
54
+ run."purpose",
55
+ run."health_sequence",
56
+ run."verified_url",
57
+ run."failure_code",
58
+ run."settled_at"
59
+ FROM "auth_runs" AS run
60
+ WHERE run."state" IN ('verified', 'failed')
61
+ AND run."settled_at" IS NOT NULL
62
+ ORDER BY run."site_auth_connection_id", run."health_sequence" DESC
63
+ )
64
+ UPDATE "site_auth_connections" AS connection
65
+ SET
66
+ "verification_state" = CASE
67
+ WHEN latest."state" = 'verified' THEN 'verified'
68
+ WHEN latest."purpose" = 'repair' THEN 'failed'
69
+ ELSE 'needs_repair'
70
+ END,
71
+ "last_verified_at" = CASE
72
+ WHEN latest."state" = 'verified' THEN latest."settled_at"
73
+ ELSE connection."last_verified_at"
74
+ END,
75
+ "last_verified_url" = CASE
76
+ WHEN latest."state" = 'verified' THEN latest."verified_url"
77
+ ELSE connection."last_verified_url"
78
+ END,
79
+ "repair_code" = CASE
80
+ WHEN latest."state" = 'verified' THEN NULL
81
+ ELSE latest."failure_code"
82
+ END,
83
+ "last_checked_at" = latest."settled_at",
84
+ "health_sequence" = latest."health_sequence"
85
+ FROM latest
86
+ WHERE connection."id" = latest."site_auth_connection_id";
87
+
88
+ UPDATE "site_auth_connections"
89
+ SET
90
+ "next_check_at" = CASE
91
+ WHEN "status" = 'active' AND "health_policy"->>'mode' = 'maintained'
92
+ THEN CASE
93
+ WHEN "last_checked_at" IS NULL THEN now()
94
+ ELSE "last_checked_at"
95
+ + ((("health_policy"->>'intervalSeconds')::bigint) * interval '1 second')
96
+ END
97
+ ELSE NULL
98
+ END,
99
+ "version" = "version" + 1,
100
+ "updated_at" = now();
101
+
102
+ ALTER TABLE "site_auth_connections"
103
+ ADD CONSTRAINT "site_auth_connections_health_check" CHECK (
104
+ (
105
+ (
106
+ "health_sequence" = 0
107
+ AND "last_checked_at" IS NULL
108
+ ) OR (
109
+ "health_sequence" > 0
110
+ AND "last_checked_at" IS NOT NULL
111
+ )
112
+ ) AND (
113
+ (
114
+ "status" = 'active'
115
+ AND "health_policy"->>'mode' = 'maintained'
116
+ AND "next_check_at" IS NOT NULL
117
+ ) OR (
118
+ NOT ("status" = 'active' AND "health_policy"->>'mode' = 'maintained')
119
+ AND "next_check_at" IS NULL
120
+ )
121
+ )
122
+ );
123
+
124
+ CREATE INDEX "site_auth_connections_health_maintenance_idx"
125
+ ON "site_auth_connections" ("workspace_id", "next_check_at", "id");
126
+
127
+ REVOKE ALL ON SEQUENCE "auth_runs_health_sequence_seq" FROM PUBLIC;
128
+ DO $browser_auth_health_role_grants$
129
+ BEGIN
130
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
131
+ EXECUTE format(
132
+ 'GRANT USAGE, SELECT ON SEQUENCE %I.auth_runs_health_sequence_seq TO opengeni_app',
133
+ current_schema()
134
+ );
135
+ END IF;
136
+ END
137
+ $browser_auth_health_role_grants$;
@@ -0,0 +1,22 @@
1
+ -- deployment-mode: maintenance
2
+ -- Extend the durable interaction-operation journal with provider-managed
3
+ -- browser authentication. No rows are rewritten and no authority is exposed.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '30s';
6
+
7
+ ALTER TABLE "interaction_resource_operations"
8
+ DROP CONSTRAINT "interaction_resource_operations_kind_check";
9
+
10
+ ALTER TABLE "interaction_resource_operations"
11
+ ADD CONSTRAINT "interaction_resource_operations_kind_check"
12
+ CHECK ("kind" IN (
13
+ 'create',
14
+ 'update',
15
+ 'start',
16
+ 'report',
17
+ 'protected_fill',
18
+ 'external_auth',
19
+ 'verify',
20
+ 'resolve',
21
+ 'save'
22
+ ));