@opengeni/db 3.6.0 → 3.7.4

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 (80) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-744DFC65.js → chunk-4NAR7FBA.js} +92 -6
  3. package/dist/chunk-4NAR7FBA.js.map +1 -0
  4. package/dist/{chunk-IW6O6G7R.js → chunk-5NNCGFQY.js} +2 -2
  5. package/dist/{chunk-G3732M3T.js → chunk-DZCYW2WT.js} +2 -2
  6. package/dist/{chunk-WW7AJ5IC.js → chunk-F6PBS6WE.js} +5 -7
  7. package/dist/chunk-F6PBS6WE.js.map +1 -0
  8. package/dist/{chunk-UJ2EW3QI.js → chunk-O56DM5GZ.js} +340 -85
  9. package/dist/chunk-O56DM5GZ.js.map +1 -0
  10. package/dist/{chunk-CDMMSUWJ.js → chunk-S3TDDCS2.js} +3 -3
  11. package/dist/{chunk-Q5YUGQVK.js → chunk-SZAWDXQC.js} +3 -3
  12. package/dist/{chunk-UGDVSQ6U.js → chunk-U4WPPEBH.js} +2 -2
  13. package/dist/{chunk-2NT476HD.js → chunk-US4IS6IV.js} +2 -2
  14. package/dist/codex-token-resolver.d.ts +12 -1
  15. package/dist/company-profile-agent-admin.d.ts +4 -2
  16. package/dist/company-profile-agent-policy.d.ts +20 -0
  17. package/dist/company-profile-schema.d.ts +573 -0
  18. package/dist/editable-artifact-durable-export.js +2 -2
  19. package/dist/editable-artifacts.js +3 -3
  20. package/dist/index.d.ts +382 -18
  21. package/dist/index.js +4513 -2065
  22. package/dist/index.js.map +1 -1
  23. package/dist/managed-auth-session-sets.js +3 -3
  24. package/dist/managed-human-provisioning.d.ts +3 -3
  25. package/dist/memory-domain.d.ts +5 -3
  26. package/dist/provision-roles.js +1 -1
  27. package/dist/runtime-posture.d.ts +5 -5
  28. package/dist/schema.d.ts +725 -4
  29. package/dist/schema.js +13 -1
  30. package/dist/session-background-commands.js +3 -3
  31. package/dist/session-control.d.ts +40 -5
  32. package/dist/session-queue-commands.d.ts +2 -0
  33. package/dist/session-tenancy.js +3 -3
  34. package/dist/user-resource-authority.d.ts +8 -0
  35. package/dist/video-generation.js +3 -3
  36. package/drizzle/0369_local_human_personal_authority.sql +350 -0
  37. package/drizzle/0370_workspace_member_management_scope.sql +98 -0
  38. package/drizzle/0371_workspace_member_candidate_inventory.sql +128 -0
  39. package/drizzle/0372_ordered_variable_set_runtime_authority.sql +287 -0
  40. package/drizzle/0373_sandbox_shared_preparation.sql +59 -0
  41. package/drizzle/0374_session_event_cursors.sql +315 -0
  42. package/drizzle/0375_session_recovery_observability.sql +73 -0
  43. package/drizzle/0376_organization_workspace_inventory.sql +88 -0
  44. package/drizzle/0377_scoped_rig_health_projection.sql +80 -0
  45. package/drizzle/0378_scoped_rig_health_audit_timestamp.sql +77 -0
  46. package/drizzle/0379_session_event_raw_lane_activation.sql +518 -0
  47. package/drizzle/0380_autonomous_company_profile_agent_policy.sql +829 -0
  48. package/drizzle/0381_organization_codex_subscription_inheritance.sql +720 -0
  49. package/drizzle/0382_organization_api_key_provenance.sql +91 -0
  50. package/drizzle/0383_codex_cooldown_reconciliation.sql +51 -0
  51. package/drizzle/0384_codex_cooldown_revision_guard_privileges.sql +6 -0
  52. package/drizzle/0385_connected_machine_legacy_tilde_workdirs.sql +25 -0
  53. package/drizzle/0386_local_organization_administration.sql +157 -0
  54. package/package.json +5 -5
  55. package/src/codex-token-resolver.ts +26 -1
  56. package/src/company-brain-governed-writes.ts +30 -1
  57. package/src/company-profile-agent-admin.ts +68 -1
  58. package/src/company-profile-agent-policy.ts +126 -0
  59. package/src/company-profile-schema.ts +115 -1
  60. package/src/database.ts +3 -5
  61. package/src/index.ts +3626 -456
  62. package/src/managed-human-provisioning.ts +3 -3
  63. package/src/memory-domain.ts +9 -4
  64. package/src/organization-membership-lifecycle.ts +0 -1
  65. package/src/provision-roles.ts +83 -4
  66. package/src/runtime-posture.ts +22 -0
  67. package/src/schema.ts +161 -8
  68. package/src/session-control.ts +177 -59
  69. package/src/session-queue-commands.ts +146 -40
  70. package/src/session-tool-call-settlement.ts +34 -7
  71. package/src/user-resource-authority.ts +56 -0
  72. package/dist/chunk-744DFC65.js.map +0 -1
  73. package/dist/chunk-UJ2EW3QI.js.map +0 -1
  74. package/dist/chunk-WW7AJ5IC.js.map +0 -1
  75. /package/dist/{chunk-IW6O6G7R.js.map → chunk-5NNCGFQY.js.map} +0 -0
  76. /package/dist/{chunk-G3732M3T.js.map → chunk-DZCYW2WT.js.map} +0 -0
  77. /package/dist/{chunk-CDMMSUWJ.js.map → chunk-S3TDDCS2.js.map} +0 -0
  78. /package/dist/{chunk-Q5YUGQVK.js.map → chunk-SZAWDXQC.js.map} +0 -0
  79. /package/dist/{chunk-UGDVSQ6U.js.map → chunk-U4WPPEBH.js.map} +0 -0
  80. /package/dist/{chunk-2NT476HD.js.map → chunk-US4IS6IV.js.map} +0 -0
@@ -0,0 +1,518 @@
1
+ -- deployment-mode: maintenance
2
+ -- Activate the cursor-authoritative raw lane only after the fleet has drained:
3
+ -- public readers move to session_event_cursors in the same release, while old
4
+ -- SQL writers remain safe through the database boundary below.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ DO $session_event_raw_lane_writer_drain_before_lock$
10
+ DECLARE
11
+ configured_roles_text text := nullif(
12
+ current_setting('opengeni.migration_application_roles', true), ''
13
+ );
14
+ configured_roles jsonb;
15
+ BEGIN
16
+ IF configured_roles_text IS NULL THEN
17
+ RAISE EXCEPTION
18
+ '0379 session event raw-lane activation requires an explicit application database role list'
19
+ USING ERRCODE = '55000';
20
+ END IF;
21
+ BEGIN
22
+ configured_roles := configured_roles_text::jsonb;
23
+ EXCEPTION WHEN OTHERS THEN
24
+ RAISE EXCEPTION
25
+ '0379 session event raw-lane activation received a malformed application database role list'
26
+ USING ERRCODE = '55000';
27
+ END;
28
+ IF jsonb_typeof(configured_roles) <> 'array'
29
+ OR jsonb_array_length(configured_roles) NOT BETWEEN 1 AND 16
30
+ OR EXISTS (
31
+ SELECT 1 FROM jsonb_array_elements(configured_roles) AS roles(value)
32
+ WHERE jsonb_typeof(value) <> 'string'
33
+ OR btrim(value #>> '{}') = ''
34
+ OR octet_length(value #>> '{}') > 63
35
+ )
36
+ OR (
37
+ SELECT count(*) FROM jsonb_array_elements_text(configured_roles)
38
+ ) <> (
39
+ SELECT count(DISTINCT value)
40
+ FROM jsonb_array_elements_text(configured_roles) AS roles(value)
41
+ )
42
+ THEN
43
+ RAISE EXCEPTION
44
+ '0379 session event raw-lane activation received an invalid application database role list'
45
+ USING ERRCODE = '55000';
46
+ END IF;
47
+ IF EXISTS (
48
+ SELECT 1 FROM pg_stat_activity activity
49
+ JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
50
+ ON roles.role_name = activity.usename
51
+ WHERE activity.datname = current_database()
52
+ AND activity.pid <> pg_backend_pid()
53
+ )
54
+ THEN
55
+ RAISE EXCEPTION
56
+ '0379 session event raw-lane activation requires all configured OpenGeni application database sessions to be stopped'
57
+ USING ERRCODE = '55000';
58
+ END IF;
59
+ END
60
+ $session_event_raw_lane_writer_drain_before_lock$;
61
+
62
+ LOCK TABLE sessions IN ACCESS EXCLUSIVE MODE;
63
+ LOCK TABLE session_event_cursors IN ACCESS EXCLUSIVE MODE;
64
+ LOCK TABLE session_events IN ACCESS EXCLUSIVE MODE;
65
+
66
+ DO $session_event_raw_lane_writer_drain_after_lock$
67
+ DECLARE
68
+ configured_roles jsonb := current_setting(
69
+ 'opengeni.migration_application_roles', false
70
+ )::jsonb;
71
+ BEGIN
72
+ IF EXISTS (
73
+ SELECT 1 FROM pg_stat_activity activity
74
+ JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
75
+ ON roles.role_name = activity.usename
76
+ WHERE activity.datname = current_database()
77
+ AND activity.pid <> pg_backend_pid()
78
+ )
79
+ THEN
80
+ RAISE EXCEPTION
81
+ '0379 session event raw-lane activation requires all configured OpenGeni application database sessions to be stopped'
82
+ USING ERRCODE = '55000';
83
+ END IF;
84
+ END
85
+ $session_event_raw_lane_writer_drain_after_lock$;
86
+
87
+ -- The migration owner must inspect the complete FORCE-RLS inventory. These
88
+ -- toggles are transaction-local in effect: any failed parity proof rolls the
89
+ -- whole migration back with FORCE still enabled.
90
+ ALTER TABLE sessions NO FORCE ROW LEVEL SECURITY;
91
+ ALTER TABLE session_event_cursors NO FORCE ROW LEVEL SECURITY;
92
+ ALTER TABLE session_events NO FORCE ROW LEVEL SECURITY;
93
+
94
+ DO $session_event_cursor_activation_parity$
95
+ DECLARE
96
+ mismatch record;
97
+ BEGIN
98
+ SELECT
99
+ session.id AS session_id,
100
+ session.last_sequence AS projected_sequence,
101
+ cursor.last_sequence AS cursor_sequence,
102
+ COUNT(event.sequence)::integer AS event_count,
103
+ MIN(event.sequence)::integer AS first_event_sequence,
104
+ COALESCE(MAX(event.sequence), 0)::integer AS event_sequence
105
+ INTO mismatch
106
+ FROM sessions session
107
+ LEFT JOIN session_event_cursors cursor
108
+ ON cursor.account_id = session.account_id
109
+ AND cursor.workspace_id = session.workspace_id
110
+ AND cursor.session_id = session.id
111
+ LEFT JOIN session_events event
112
+ ON event.account_id = session.account_id
113
+ AND event.workspace_id = session.workspace_id
114
+ AND event.session_id = session.id
115
+ GROUP BY session.id, session.last_sequence, cursor.last_sequence
116
+ HAVING cursor.last_sequence IS NULL
117
+ OR COUNT(event.sequence)::integer <> cursor.last_sequence
118
+ OR cursor.last_sequence <> COALESCE(MAX(event.sequence), 0)::integer
119
+ OR (
120
+ cursor.last_sequence = 0
121
+ AND MIN(event.sequence) IS NOT NULL
122
+ )
123
+ OR (
124
+ cursor.last_sequence > 0
125
+ AND MIN(event.sequence)::integer <> 1
126
+ )
127
+ OR session.last_sequence > cursor.last_sequence
128
+ LIMIT 1;
129
+
130
+ IF FOUND THEN
131
+ RAISE EXCEPTION USING
132
+ ERRCODE = '55000',
133
+ MESSAGE = 'session event raw-lane activation refused because cursor parity failed',
134
+ DETAIL = pg_catalog.format(
135
+ 'session_id=%s session=%s cursor=%s count=%s first=%s event=%s',
136
+ mismatch.session_id,
137
+ mismatch.projected_sequence,
138
+ mismatch.cursor_sequence,
139
+ mismatch.event_count,
140
+ mismatch.first_event_sequence,
141
+ mismatch.event_sequence
142
+ );
143
+ END IF;
144
+ END
145
+ $session_event_cursor_activation_parity$;
146
+
147
+ ALTER TABLE session_events FORCE ROW LEVEL SECURITY;
148
+ ALTER TABLE session_event_cursors FORCE ROW LEVEL SECURITY;
149
+ ALTER TABLE sessions FORCE ROW LEVEL SECURITY;
150
+
151
+ CREATE FUNCTION normalize_legacy_session_event_sequence_from_cursor()
152
+ RETURNS trigger
153
+ LANGUAGE plpgsql
154
+ SECURITY DEFINER
155
+ SET search_path FROM CURRENT
156
+ AS $normalize_legacy_session_event_sequence_from_cursor$
157
+ DECLARE
158
+ fenced_access_capability_id uuid;
159
+ current_sequence integer;
160
+ raw_range jsonb;
161
+ BEGIN
162
+ raw_range := nullif(
163
+ pg_catalog.current_setting('opengeni.session_event_raw_cursor_range_v1', true),
164
+ ''
165
+ )::jsonb;
166
+ IF NEW.type IN (
167
+ 'agent.message.delta',
168
+ 'agent.reasoning.delta',
169
+ 'sandbox.command.output.delta',
170
+ 'terminal.pty.output.delta'
171
+ )
172
+ AND raw_range ->> 'accountId' = NEW.account_id::text
173
+ AND raw_range ->> 'workspaceId' = NEW.workspace_id::text
174
+ AND raw_range ->> 'sessionId' = NEW.session_id::text
175
+ AND NEW.sequence BETWEEN
176
+ (raw_range ->> 'firstSequence')::integer
177
+ AND (raw_range ->> 'lastSequence')::integer
178
+ THEN
179
+ RETURN NEW;
180
+ END IF;
181
+
182
+ PERFORM pg_catalog.set_config(
183
+ 'opengeni.session_variable_set_attachments_v1',
184
+ '1',
185
+ true
186
+ );
187
+ fenced_access_capability_id :=
188
+ opengeni_private.open_session_tenancy_fenced_access(
189
+ session_tenancy_fence_target_schema()
190
+ );
191
+ SELECT cursor.last_sequence
192
+ INTO current_sequence
193
+ FROM session_event_cursors cursor
194
+ WHERE cursor.account_id = NEW.account_id
195
+ AND cursor.workspace_id = NEW.workspace_id
196
+ AND cursor.session_id = NEW.session_id
197
+ FOR UPDATE;
198
+ IF NOT FOUND THEN
199
+ RAISE EXCEPTION USING
200
+ ERRCODE = '55000',
201
+ MESSAGE = 'session event cursor is missing for a legacy event writer';
202
+ END IF;
203
+
204
+ -- New cursor writers submit the complete contiguous range and are validated
205
+ -- by the AFTER STATEMENT trigger. A legacy writer can submit the stale
206
+ -- sessions.last_sequence + 1 after raw traffic; rebase only that already-
207
+ -- consumed value. Multi-row gaps still fail closed in the statement trigger.
208
+ IF NEW.sequence <= current_sequence THEN
209
+ NEW.sequence := current_sequence + 1;
210
+ END IF;
211
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
212
+ fenced_access_capability_id
213
+ );
214
+ RETURN NEW;
215
+ EXCEPTION WHEN OTHERS THEN
216
+ IF fenced_access_capability_id IS NOT NULL THEN
217
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
218
+ fenced_access_capability_id
219
+ );
220
+ END IF;
221
+ RAISE;
222
+ END
223
+ $normalize_legacy_session_event_sequence_from_cursor$;
224
+
225
+ CREATE FUNCTION prevent_session_event_projection_regression()
226
+ RETURNS trigger
227
+ LANGUAGE plpgsql
228
+ SECURITY DEFINER
229
+ SET search_path FROM CURRENT
230
+ AS $prevent_session_event_projection_regression$
231
+ DECLARE
232
+ fenced_access_capability_id uuid;
233
+ current_sequence integer;
234
+ BEGIN
235
+ IF NEW.last_sequence IS NOT DISTINCT FROM OLD.last_sequence THEN
236
+ RETURN NEW;
237
+ END IF;
238
+ PERFORM pg_catalog.set_config(
239
+ 'opengeni.session_variable_set_attachments_v1',
240
+ '1',
241
+ true
242
+ );
243
+ fenced_access_capability_id :=
244
+ opengeni_private.open_session_tenancy_fenced_access(
245
+ session_tenancy_fence_target_schema()
246
+ );
247
+ SELECT cursor.last_sequence
248
+ INTO current_sequence
249
+ FROM session_event_cursors cursor
250
+ WHERE cursor.account_id = NEW.account_id
251
+ AND cursor.workspace_id = NEW.workspace_id
252
+ AND cursor.session_id = NEW.id
253
+ FOR UPDATE;
254
+ IF NOT FOUND THEN
255
+ RAISE EXCEPTION USING
256
+ ERRCODE = '55000',
257
+ MESSAGE = 'session event cursor is missing for a session projection update';
258
+ END IF;
259
+ -- Event insertion is the only cursor advance. A semantic writer that updates
260
+ -- session state before its event is snapped to the current cursor, and the
261
+ -- validated AFTER INSERT trigger advances the compatibility projection once
262
+ -- the event exists. No direct/manual update may lead or regress authority.
263
+ NEW.last_sequence := current_sequence;
264
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
265
+ fenced_access_capability_id
266
+ );
267
+ RETURN NEW;
268
+ EXCEPTION WHEN OTHERS THEN
269
+ IF fenced_access_capability_id IS NOT NULL THEN
270
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
271
+ fenced_access_capability_id
272
+ );
273
+ END IF;
274
+ RAISE;
275
+ END
276
+ $prevent_session_event_projection_regression$;
277
+
278
+ CREATE FUNCTION assert_session_event_projection_not_ahead()
279
+ RETURNS trigger
280
+ LANGUAGE plpgsql
281
+ SECURITY DEFINER
282
+ SET search_path FROM CURRENT
283
+ AS $assert_session_event_projection_not_ahead$
284
+ DECLARE
285
+ fenced_access_capability_id uuid;
286
+ current_sequence integer;
287
+ BEGIN
288
+ PERFORM pg_catalog.set_config(
289
+ 'opengeni.session_variable_set_attachments_v1',
290
+ '1',
291
+ true
292
+ );
293
+ fenced_access_capability_id :=
294
+ opengeni_private.open_session_tenancy_fenced_access(
295
+ session_tenancy_fence_target_schema()
296
+ );
297
+ SELECT cursor.last_sequence
298
+ INTO current_sequence
299
+ FROM session_event_cursors cursor
300
+ WHERE cursor.account_id = NEW.account_id
301
+ AND cursor.workspace_id = NEW.workspace_id
302
+ AND cursor.session_id = NEW.id;
303
+ IF NOT FOUND THEN
304
+ -- A session inserted and then deleted in the same transaction legitimately
305
+ -- loses its cursor through ON DELETE CASCADE before this deferred trigger
306
+ -- runs. Consult the wider row only on that exceptional path; ordinary
307
+ -- validation stays entirely on the narrow authoritative cursor and does
308
+ -- not evaluate unrelated sessions RLS protocol policies.
309
+ PERFORM 1 FROM sessions session
310
+ WHERE session.account_id = NEW.account_id
311
+ AND session.workspace_id = NEW.workspace_id
312
+ AND session.id = NEW.id;
313
+ IF NOT FOUND THEN
314
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
315
+ fenced_access_capability_id
316
+ );
317
+ RETURN NULL;
318
+ END IF;
319
+ RAISE EXCEPTION USING
320
+ ERRCODE = '55000',
321
+ MESSAGE = 'session event compatibility projection has no cursor',
322
+ DETAIL = pg_catalog.format(
323
+ 'session_id=%s session=%s',
324
+ NEW.id,
325
+ NEW.last_sequence
326
+ );
327
+ END IF;
328
+ IF NEW.last_sequence > current_sequence THEN
329
+ RAISE EXCEPTION USING
330
+ ERRCODE = '55000',
331
+ MESSAGE = 'session event compatibility projection is ahead of its cursor',
332
+ DETAIL = pg_catalog.format(
333
+ 'session_id=%s session=%s cursor=%s',
334
+ NEW.id,
335
+ NEW.last_sequence,
336
+ current_sequence
337
+ );
338
+ END IF;
339
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
340
+ fenced_access_capability_id
341
+ );
342
+ RETURN NULL;
343
+ EXCEPTION WHEN OTHERS THEN
344
+ IF fenced_access_capability_id IS NOT NULL THEN
345
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
346
+ fenced_access_capability_id
347
+ );
348
+ END IF;
349
+ RAISE;
350
+ END
351
+ $assert_session_event_projection_not_ahead$;
352
+
353
+ CREATE OR REPLACE FUNCTION advance_session_event_cursors_for_inserted_events()
354
+ RETURNS trigger
355
+ LANGUAGE plpgsql
356
+ SECURITY DEFINER
357
+ SET search_path FROM CURRENT
358
+ AS $advance_session_event_cursors$
359
+ DECLARE
360
+ fenced_access_capability_id uuid;
361
+ inserted_group record;
362
+ current_sequence integer;
363
+ BEGIN
364
+ PERFORM pg_catalog.set_config(
365
+ 'opengeni.session_variable_set_attachments_v1',
366
+ '1',
367
+ true
368
+ );
369
+ fenced_access_capability_id :=
370
+ opengeni_private.open_session_tenancy_fenced_access(
371
+ session_tenancy_fence_target_schema()
372
+ );
373
+ FOR inserted_group IN
374
+ SELECT
375
+ account_id,
376
+ workspace_id,
377
+ session_id,
378
+ MIN(sequence)::integer AS first_sequence,
379
+ MAX(sequence)::integer AS last_sequence,
380
+ COUNT(*)::integer AS sequence_count,
381
+ COUNT(DISTINCT sequence)::integer AS distinct_sequence_count,
382
+ BOOL_OR(type NOT IN (
383
+ 'agent.message.delta',
384
+ 'agent.reasoning.delta',
385
+ 'sandbox.command.output.delta',
386
+ 'terminal.pty.output.delta'
387
+ )) AS advances_activity
388
+ FROM inserted_session_events
389
+ GROUP BY account_id, workspace_id, session_id
390
+ ORDER BY workspace_id, session_id
391
+ LOOP
392
+ SELECT last_sequence
393
+ INTO current_sequence
394
+ FROM session_event_cursors
395
+ WHERE account_id = inserted_group.account_id
396
+ AND workspace_id = inserted_group.workspace_id
397
+ AND session_id = inserted_group.session_id
398
+ FOR UPDATE;
399
+
400
+ IF NOT FOUND THEN
401
+ RAISE EXCEPTION USING
402
+ ERRCODE = '55000',
403
+ MESSAGE = 'session event cursor is missing for an inserted event';
404
+ END IF;
405
+ IF inserted_group.distinct_sequence_count <> inserted_group.sequence_count
406
+ OR inserted_group.first_sequence <> current_sequence + 1
407
+ OR inserted_group.last_sequence <> current_sequence + inserted_group.sequence_count
408
+ THEN
409
+ RAISE EXCEPTION USING
410
+ ERRCODE = '55000',
411
+ MESSAGE = 'session event insert does not exactly continue the durable cursor',
412
+ DETAIL = pg_catalog.format(
413
+ 'workspace_id=%s session_id=%s current=%s first=%s last=%s count=%s distinct=%s',
414
+ inserted_group.workspace_id,
415
+ inserted_group.session_id,
416
+ current_sequence,
417
+ inserted_group.first_sequence,
418
+ inserted_group.last_sequence,
419
+ inserted_group.sequence_count,
420
+ inserted_group.distinct_sequence_count
421
+ );
422
+ END IF;
423
+
424
+ UPDATE session_event_cursors
425
+ SET last_sequence = inserted_group.last_sequence,
426
+ revision = revision + 1,
427
+ updated_at = pg_catalog.now()
428
+ WHERE account_id = inserted_group.account_id
429
+ AND workspace_id = inserted_group.workspace_id
430
+ AND session_id = inserted_group.session_id
431
+ AND last_sequence = current_sequence;
432
+ IF NOT FOUND THEN
433
+ RAISE EXCEPTION USING
434
+ ERRCODE = '40001',
435
+ MESSAGE = 'session event cursor changed while applying an inserted event statement';
436
+ END IF;
437
+
438
+ -- The wide column remains a semantic compatibility projection only. Raw
439
+ -- batches leave it untouched; semantic SQL functions and old binaries are
440
+ -- synchronized here even when they started from a stale projection.
441
+ IF inserted_group.advances_activity THEN
442
+ UPDATE sessions session
443
+ SET last_sequence = inserted_group.last_sequence
444
+ WHERE session.account_id = inserted_group.account_id
445
+ AND session.workspace_id = inserted_group.workspace_id
446
+ AND session.id = inserted_group.session_id;
447
+ IF NOT FOUND THEN
448
+ RAISE EXCEPTION USING
449
+ ERRCODE = '55000',
450
+ MESSAGE = 'session projection is missing for an inserted semantic event';
451
+ END IF;
452
+ END IF;
453
+ END LOOP;
454
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
455
+ fenced_access_capability_id
456
+ );
457
+ RETURN NULL;
458
+ EXCEPTION WHEN OTHERS THEN
459
+ IF fenced_access_capability_id IS NOT NULL THEN
460
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
461
+ fenced_access_capability_id
462
+ );
463
+ END IF;
464
+ RAISE;
465
+ END
466
+ $advance_session_event_cursors$;
467
+
468
+ DO $session_event_raw_lane_function_paths$
469
+ DECLARE
470
+ target_schema text := pg_catalog.current_schema();
471
+ BEGIN
472
+ EXECUTE pg_catalog.format(
473
+ 'ALTER FUNCTION %I.normalize_legacy_session_event_sequence_from_cursor() '
474
+ || 'SET search_path = pg_catalog, %I, pg_temp',
475
+ target_schema,
476
+ target_schema
477
+ );
478
+ EXECUTE pg_catalog.format(
479
+ 'ALTER FUNCTION %I.prevent_session_event_projection_regression() '
480
+ || 'SET search_path = pg_catalog, %I, pg_temp',
481
+ target_schema,
482
+ target_schema
483
+ );
484
+ EXECUTE pg_catalog.format(
485
+ 'ALTER FUNCTION %I.assert_session_event_projection_not_ahead() '
486
+ || 'SET search_path = pg_catalog, %I, pg_temp',
487
+ target_schema,
488
+ target_schema
489
+ );
490
+ EXECUTE pg_catalog.format(
491
+ 'ALTER FUNCTION %I.advance_session_event_cursors_for_inserted_events() '
492
+ || 'SET search_path = pg_catalog, %I, pg_temp',
493
+ target_schema,
494
+ target_schema
495
+ );
496
+ END
497
+ $session_event_raw_lane_function_paths$;
498
+
499
+ REVOKE ALL ON FUNCTION normalize_legacy_session_event_sequence_from_cursor() FROM PUBLIC;
500
+ REVOKE ALL ON FUNCTION prevent_session_event_projection_regression() FROM PUBLIC;
501
+ REVOKE ALL ON FUNCTION assert_session_event_projection_not_ahead() FROM PUBLIC;
502
+ REVOKE ALL ON FUNCTION advance_session_event_cursors_for_inserted_events() FROM PUBLIC;
503
+
504
+ CREATE TRIGGER session_events_normalize_legacy_cursor_sequence
505
+ BEFORE INSERT ON session_events
506
+ FOR EACH ROW
507
+ EXECUTE FUNCTION normalize_legacy_session_event_sequence_from_cursor();
508
+
509
+ CREATE TRIGGER sessions_prevent_event_projection_regression
510
+ BEFORE UPDATE OF last_sequence ON sessions
511
+ FOR EACH ROW
512
+ EXECUTE FUNCTION prevent_session_event_projection_regression();
513
+
514
+ CREATE CONSTRAINT TRIGGER sessions_event_projection_cursor_guard
515
+ AFTER INSERT OR UPDATE OF last_sequence ON sessions
516
+ DEFERRABLE INITIALLY DEFERRED
517
+ FOR EACH ROW
518
+ EXECUTE FUNCTION assert_session_event_projection_not_ahead();