@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.
- package/dist/canonical-human-identities.js +3 -3
- package/dist/{chunk-744DFC65.js → chunk-4NAR7FBA.js} +92 -6
- package/dist/chunk-4NAR7FBA.js.map +1 -0
- package/dist/{chunk-IW6O6G7R.js → chunk-5NNCGFQY.js} +2 -2
- package/dist/{chunk-G3732M3T.js → chunk-DZCYW2WT.js} +2 -2
- package/dist/{chunk-WW7AJ5IC.js → chunk-F6PBS6WE.js} +5 -7
- package/dist/chunk-F6PBS6WE.js.map +1 -0
- package/dist/{chunk-UJ2EW3QI.js → chunk-O56DM5GZ.js} +340 -85
- package/dist/chunk-O56DM5GZ.js.map +1 -0
- package/dist/{chunk-CDMMSUWJ.js → chunk-S3TDDCS2.js} +3 -3
- package/dist/{chunk-Q5YUGQVK.js → chunk-SZAWDXQC.js} +3 -3
- package/dist/{chunk-UGDVSQ6U.js → chunk-U4WPPEBH.js} +2 -2
- package/dist/{chunk-2NT476HD.js → chunk-US4IS6IV.js} +2 -2
- package/dist/codex-token-resolver.d.ts +12 -1
- package/dist/company-profile-agent-admin.d.ts +4 -2
- package/dist/company-profile-agent-policy.d.ts +20 -0
- package/dist/company-profile-schema.d.ts +573 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +382 -18
- package/dist/index.js +4513 -2065
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/managed-human-provisioning.d.ts +3 -3
- package/dist/memory-domain.d.ts +5 -3
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +5 -5
- package/dist/schema.d.ts +725 -4
- package/dist/schema.js +13 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-control.d.ts +40 -5
- package/dist/session-queue-commands.d.ts +2 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/user-resource-authority.d.ts +8 -0
- package/dist/video-generation.js +3 -3
- package/drizzle/0369_local_human_personal_authority.sql +350 -0
- package/drizzle/0370_workspace_member_management_scope.sql +98 -0
- package/drizzle/0371_workspace_member_candidate_inventory.sql +128 -0
- package/drizzle/0372_ordered_variable_set_runtime_authority.sql +287 -0
- package/drizzle/0373_sandbox_shared_preparation.sql +59 -0
- package/drizzle/0374_session_event_cursors.sql +315 -0
- package/drizzle/0375_session_recovery_observability.sql +73 -0
- package/drizzle/0376_organization_workspace_inventory.sql +88 -0
- package/drizzle/0377_scoped_rig_health_projection.sql +80 -0
- package/drizzle/0378_scoped_rig_health_audit_timestamp.sql +77 -0
- package/drizzle/0379_session_event_raw_lane_activation.sql +518 -0
- package/drizzle/0380_autonomous_company_profile_agent_policy.sql +829 -0
- package/drizzle/0381_organization_codex_subscription_inheritance.sql +720 -0
- package/drizzle/0382_organization_api_key_provenance.sql +91 -0
- package/drizzle/0383_codex_cooldown_reconciliation.sql +51 -0
- package/drizzle/0384_codex_cooldown_revision_guard_privileges.sql +6 -0
- package/drizzle/0385_connected_machine_legacy_tilde_workdirs.sql +25 -0
- package/drizzle/0386_local_organization_administration.sql +157 -0
- package/package.json +5 -5
- package/src/codex-token-resolver.ts +26 -1
- package/src/company-brain-governed-writes.ts +30 -1
- package/src/company-profile-agent-admin.ts +68 -1
- package/src/company-profile-agent-policy.ts +126 -0
- package/src/company-profile-schema.ts +115 -1
- package/src/database.ts +3 -5
- package/src/index.ts +3626 -456
- package/src/managed-human-provisioning.ts +3 -3
- package/src/memory-domain.ts +9 -4
- package/src/organization-membership-lifecycle.ts +0 -1
- package/src/provision-roles.ts +83 -4
- package/src/runtime-posture.ts +22 -0
- package/src/schema.ts +161 -8
- package/src/session-control.ts +177 -59
- package/src/session-queue-commands.ts +146 -40
- package/src/session-tool-call-settlement.ts +34 -7
- package/src/user-resource-authority.ts +56 -0
- package/dist/chunk-744DFC65.js.map +0 -1
- package/dist/chunk-UJ2EW3QI.js.map +0 -1
- package/dist/chunk-WW7AJ5IC.js.map +0 -1
- /package/dist/{chunk-IW6O6G7R.js.map → chunk-5NNCGFQY.js.map} +0 -0
- /package/dist/{chunk-G3732M3T.js.map → chunk-DZCYW2WT.js.map} +0 -0
- /package/dist/{chunk-CDMMSUWJ.js.map → chunk-S3TDDCS2.js.map} +0 -0
- /package/dist/{chunk-Q5YUGQVK.js.map → chunk-SZAWDXQC.js.map} +0 -0
- /package/dist/{chunk-UGDVSQ6U.js.map → chunk-U4WPPEBH.js.map} +0 -0
- /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();
|