@opengeni/db 0.9.3 → 0.12.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.
- package/dist/{chunk-4LG5NBTC.js → chunk-VUKRIBO5.js} +577 -12
- package/dist/chunk-VUKRIBO5.js.map +1 -0
- package/dist/{chunk-KW526IJA.js → chunk-Y5WZZVQK.js} +80 -4
- package/dist/chunk-Y5WZZVQK.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6372 -2189
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +6 -3
- package/dist/migrate.js +1 -1
- package/dist/provision-roles.d.ts +1122 -91
- package/dist/{schema-CdPGTHlD.d.ts → schema-CnpD6BcX.d.ts} +5908 -3626
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +19 -1
- package/drizzle/0053_codex_credential_leases.sql +2 -2
- package/drizzle/0057_durable_queue_control.sql +1 -1
- package/drizzle/0061_session_workflow_wake_outbox.sql +1 -1
- package/drizzle/0062_session_list_snapshot_reaper.sql +1 -1
- package/drizzle/0063_session_control_mega_foundation.sql +1 -1
- package/drizzle/0064_rotation_strategy_sharded_backfill.sql +1 -1
- package/drizzle/0065_codex_subscription_overview.sql +168 -0
- package/drizzle/0065_session_tool_policy.sql +38 -0
- package/drizzle/0067_session_event_payload_bounds.sql +2 -2
- package/drizzle/0068_workspace_control_event_bounds.sql +2 -2
- package/drizzle/0069_session_event_history_backfill.sql +2 -2
- package/drizzle/0074_session_activity_revisions.sql +2 -2
- package/drizzle/0106_session_attempt_mcp_approval_policies.sql +29 -0
- package/drizzle/0107_host_export_lineage_contract.sql +381 -0
- package/drizzle/0108_fence_invalidated_warming_epochs.sql +76 -0
- package/drizzle/0109_nested_agent_depth_expand.sql +42 -0
- package/drizzle/0110_nested_agent_depth_boundary.sql +480 -0
- package/drizzle/0111_nested_agent_depth_backfill.sql +49 -0
- package/drizzle/0112_nested_agent_depth_contract.sql +38 -0
- package/drizzle/0113_nested_agent_depth_validate.sql +13 -0
- package/drizzle/0114_nested_agent_depth_contract.sql +49 -0
- package/drizzle/0115_nested_agent_depth_validate.sql +11 -0
- package/drizzle/0116_nested_agent_depth_index.sql +4 -0
- package/drizzle/0117_sandbox_recovery_generations.sql +699 -0
- package/drizzle/0118_new_session_drafts.sql +59 -0
- package/drizzle/0119_pending_tool_output_policy.sql +5 -0
- package/drizzle/0120_durable_goal_wake.sql +360 -0
- package/drizzle/0121_goal_update_idempotency.sql +11 -0
- package/package.json +5 -4
- package/src/codex-token-resolver.ts +175 -14
- package/src/connection-token-resolver.ts +143 -120
- package/src/event-payload-sanitizer.ts +32 -2
- package/src/index.ts +7734 -1330
- package/src/migrate.ts +131 -2
- package/src/new-session-drafts.ts +144 -0
- package/src/schema.ts +626 -16
- package/src/session-control.ts +44 -18
- package/src/session-queue-commands.ts +94 -21
- package/src/session-tool-call-settlement.ts +6 -1
- package/dist/chunk-4LG5NBTC.js.map +0 -1
- package/dist/chunk-KW526IJA.js.map +0 -1
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Forward-only repair for published 0097/0103/0104 histories. Immutable root
|
|
3
|
+
-- capture already exists after 0103; this migration linearizes first-consumer
|
|
4
|
+
-- enablement with deferred producers and validates the session/root invariant.
|
|
5
|
+
-- It never derives or rewrites historical lineage. Rows that may have been
|
|
6
|
+
-- populated by the old 0104 backfill require explicit evidence-backed
|
|
7
|
+
-- disposition before retry.
|
|
8
|
+
|
|
9
|
+
SET LOCAL lock_timeout = '5s';
|
|
10
|
+
SET LOCAL statement_timeout = '5min';
|
|
11
|
+
|
|
12
|
+
-- Replacing these SECURITY DEFINER functions preserves their identity, owner,
|
|
13
|
+
-- and existing exporter ACL while making the config-row lock the commit-order
|
|
14
|
+
-- boundary between first-consumer registration and deferred source capture.
|
|
15
|
+
DO $migration$
|
|
16
|
+
DECLARE target_schema text := current_schema();
|
|
17
|
+
BEGIN
|
|
18
|
+
EXECUTE format($create$
|
|
19
|
+
CREATE OR REPLACE FUNCTION opengeni_private.enqueue_host_session_event_export()
|
|
20
|
+
RETURNS trigger
|
|
21
|
+
LANGUAGE plpgsql
|
|
22
|
+
SECURITY DEFINER
|
|
23
|
+
SET search_path = pg_catalog
|
|
24
|
+
AS $function$
|
|
25
|
+
DECLARE
|
|
26
|
+
v_enabled boolean;
|
|
27
|
+
v_initiator jsonb;
|
|
28
|
+
v_context jsonb := '{}'::jsonb;
|
|
29
|
+
v_origin text;
|
|
30
|
+
v_payload_bytes integer;
|
|
31
|
+
BEGIN
|
|
32
|
+
IF NEW.type IN (
|
|
33
|
+
'agent.message.delta', 'agent.reasoning.delta',
|
|
34
|
+
'sandbox.command.output.delta', 'terminal.pty.output.delta'
|
|
35
|
+
) THEN
|
|
36
|
+
RETURN NEW;
|
|
37
|
+
END IF;
|
|
38
|
+
|
|
39
|
+
SELECT c.session_events_enabled INTO v_enabled
|
|
40
|
+
FROM %1$I.host_export_config c WHERE c.id = 1
|
|
41
|
+
FOR SHARE;
|
|
42
|
+
IF coalesce(v_enabled, false) = false THEN
|
|
43
|
+
RETURN NEW;
|
|
44
|
+
END IF;
|
|
45
|
+
|
|
46
|
+
IF NEW.turn_id IS NOT NULL THEN
|
|
47
|
+
SELECT
|
|
48
|
+
CASE WHEN octet_length(t.initiator_subject_id) <= 1024 THEN
|
|
49
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
50
|
+
'kind', t.initiator_kind,
|
|
51
|
+
'subjectId', t.initiator_subject_id,
|
|
52
|
+
'label', CASE
|
|
53
|
+
WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
|
|
54
|
+
THEN left(t.initiator_context ->> 'label', 256)
|
|
55
|
+
ELSE NULL
|
|
56
|
+
END
|
|
57
|
+
))
|
|
58
|
+
ELSE NULL END,
|
|
59
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
60
|
+
'label', CASE
|
|
61
|
+
WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
|
|
62
|
+
THEN left(t.initiator_context ->> 'label', 256)
|
|
63
|
+
ELSE NULL
|
|
64
|
+
END,
|
|
65
|
+
'backfill', CASE
|
|
66
|
+
WHEN jsonb_typeof(t.initiator_context -> 'backfill') = 'boolean'
|
|
67
|
+
THEN t.initiator_context -> 'backfill'
|
|
68
|
+
ELSE NULL
|
|
69
|
+
END,
|
|
70
|
+
'attributionOmitted', CASE
|
|
71
|
+
WHEN octet_length(t.initiator_subject_id) > 1024 THEN 'subject_id_too_large'
|
|
72
|
+
ELSE NULL
|
|
73
|
+
END
|
|
74
|
+
)),
|
|
75
|
+
t.source
|
|
76
|
+
INTO v_initiator, v_context, v_origin
|
|
77
|
+
FROM %1$I.session_turns t
|
|
78
|
+
WHERE t.workspace_id = NEW.workspace_id AND t.id = NEW.turn_id;
|
|
79
|
+
ELSIF NEW.type = 'session.created' THEN
|
|
80
|
+
SELECT
|
|
81
|
+
CASE WHEN octet_length(s.created_by_subject_id) <= 1024 THEN
|
|
82
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
83
|
+
'kind', s.created_by_kind,
|
|
84
|
+
'subjectId', s.created_by_subject_id,
|
|
85
|
+
'label', CASE
|
|
86
|
+
WHEN jsonb_typeof(s.created_by_context -> 'label') = 'string'
|
|
87
|
+
THEN left(s.created_by_context ->> 'label', 256)
|
|
88
|
+
ELSE NULL
|
|
89
|
+
END
|
|
90
|
+
))
|
|
91
|
+
ELSE NULL END,
|
|
92
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
93
|
+
'label', CASE
|
|
94
|
+
WHEN jsonb_typeof(s.created_by_context -> 'label') = 'string'
|
|
95
|
+
THEN left(s.created_by_context ->> 'label', 256)
|
|
96
|
+
ELSE NULL
|
|
97
|
+
END,
|
|
98
|
+
'backfill', CASE
|
|
99
|
+
WHEN jsonb_typeof(s.created_by_context -> 'backfill') = 'boolean'
|
|
100
|
+
THEN s.created_by_context -> 'backfill'
|
|
101
|
+
ELSE NULL
|
|
102
|
+
END,
|
|
103
|
+
'attributionOmitted', CASE
|
|
104
|
+
WHEN octet_length(s.created_by_subject_id) > 1024 THEN 'subject_id_too_large'
|
|
105
|
+
ELSE NULL
|
|
106
|
+
END
|
|
107
|
+
)),
|
|
108
|
+
NULL
|
|
109
|
+
INTO v_initiator, v_context, v_origin
|
|
110
|
+
FROM %1$I.sessions s
|
|
111
|
+
WHERE s.workspace_id = NEW.workspace_id AND s.id = NEW.session_id;
|
|
112
|
+
END IF;
|
|
113
|
+
|
|
114
|
+
v_payload_bytes := octet_length(NEW.payload::text)
|
|
115
|
+
+ octet_length(NEW.type)
|
|
116
|
+
+ coalesce(octet_length(NEW.client_event_id), 0)
|
|
117
|
+
+ coalesce(octet_length(NEW.turn_association), 0)
|
|
118
|
+
+ coalesce(octet_length(NEW.duplicate_reason), 0)
|
|
119
|
+
+ octet_length(coalesce(v_initiator, 'null'::jsonb)::text)
|
|
120
|
+
+ octet_length(v_context::text)
|
|
121
|
+
+ 768;
|
|
122
|
+
INSERT INTO %1$I.host_export_outbox (
|
|
123
|
+
export_kind, source_id, account_id, workspace_id, session_id,
|
|
124
|
+
turn_id, turn_generation, turn_attempt_id, session_sequence,
|
|
125
|
+
client_event_id, turn_association, duplicate_of_event_id, duplicate_reason,
|
|
126
|
+
event_type, idempotency_key, initiator, initiator_context, origin,
|
|
127
|
+
payload, envelope_bytes, occurred_at, source_recorded_at, enqueued_at
|
|
128
|
+
) VALUES (
|
|
129
|
+
'session_event', NEW.id, NEW.account_id, NEW.workspace_id, NEW.session_id,
|
|
130
|
+
NEW.turn_id, NEW.turn_generation, NEW.turn_attempt_id, NEW.sequence,
|
|
131
|
+
NEW.client_event_id, NEW.turn_association, NEW.duplicate_of_event_id,
|
|
132
|
+
NEW.duplicate_reason,
|
|
133
|
+
NEW.type, 'session_event:' || NEW.id::text, v_initiator,
|
|
134
|
+
coalesce(v_context, '{}'::jsonb), v_origin, NEW.payload,
|
|
135
|
+
greatest(1, v_payload_bytes), NEW.occurred_at,
|
|
136
|
+
NEW.created_at, clock_timestamp()
|
|
137
|
+
)
|
|
138
|
+
ON CONFLICT (export_kind, source_id) DO NOTHING;
|
|
139
|
+
RETURN NEW;
|
|
140
|
+
END $function$;
|
|
141
|
+
$create$, target_schema);
|
|
142
|
+
|
|
143
|
+
EXECUTE format($create$
|
|
144
|
+
CREATE OR REPLACE FUNCTION opengeni_private.enqueue_host_usage_event_export()
|
|
145
|
+
RETURNS trigger
|
|
146
|
+
LANGUAGE plpgsql
|
|
147
|
+
SECURITY DEFINER
|
|
148
|
+
SET search_path = pg_catalog
|
|
149
|
+
AS $function$
|
|
150
|
+
DECLARE
|
|
151
|
+
v_enabled boolean;
|
|
152
|
+
v_initiator jsonb := CASE
|
|
153
|
+
WHEN NEW.initiator_kind IS NOT NULL THEN jsonb_strip_nulls(jsonb_build_object(
|
|
154
|
+
'kind', NEW.initiator_kind,
|
|
155
|
+
'subjectId', NEW.initiator_subject_id,
|
|
156
|
+
'label', CASE
|
|
157
|
+
WHEN jsonb_typeof(NEW.initiator_context -> 'label') = 'string'
|
|
158
|
+
THEN left(NEW.initiator_context ->> 'label', 256)
|
|
159
|
+
ELSE NULL
|
|
160
|
+
END
|
|
161
|
+
))
|
|
162
|
+
ELSE NULL
|
|
163
|
+
END;
|
|
164
|
+
v_context jsonb := jsonb_strip_nulls(jsonb_build_object(
|
|
165
|
+
'label', CASE
|
|
166
|
+
WHEN jsonb_typeof(NEW.initiator_context -> 'label') = 'string'
|
|
167
|
+
THEN left(NEW.initiator_context ->> 'label', 256)
|
|
168
|
+
ELSE NULL
|
|
169
|
+
END,
|
|
170
|
+
'backfill', CASE
|
|
171
|
+
WHEN jsonb_typeof(NEW.initiator_context -> 'backfill') = 'boolean'
|
|
172
|
+
THEN NEW.initiator_context -> 'backfill'
|
|
173
|
+
ELSE NULL
|
|
174
|
+
END
|
|
175
|
+
));
|
|
176
|
+
v_origin text := NEW.origin;
|
|
177
|
+
v_payload jsonb;
|
|
178
|
+
v_payload_bytes integer;
|
|
179
|
+
BEGIN
|
|
180
|
+
SELECT c.usage_events_enabled INTO v_enabled
|
|
181
|
+
FROM %1$I.host_export_config c WHERE c.id = 1
|
|
182
|
+
FOR SHARE;
|
|
183
|
+
IF coalesce(v_enabled, false) = false THEN
|
|
184
|
+
RETURN NEW;
|
|
185
|
+
END IF;
|
|
186
|
+
|
|
187
|
+
-- Export-specific bounds apply only while the optional host stream is
|
|
188
|
+
-- enabled. Standalone/custom usage writers retain their historical
|
|
189
|
+
-- behavior; an embedded writer that cannot be represented fails its
|
|
190
|
+
-- source transaction visibly instead of committing a poison outbox row.
|
|
191
|
+
IF octet_length(NEW.event_type) NOT BETWEEN 1 AND 256
|
|
192
|
+
OR octet_length(NEW.unit) NOT BETWEEN 1 AND 128
|
|
193
|
+
OR (NEW.subject_id IS NOT NULL AND octet_length(NEW.subject_id) > 1024)
|
|
194
|
+
OR (NEW.source_resource_type IS NOT NULL
|
|
195
|
+
AND octet_length(NEW.source_resource_type) > 256)
|
|
196
|
+
OR (NEW.source_resource_id IS NOT NULL
|
|
197
|
+
AND octet_length(NEW.source_resource_id) > 2048)
|
|
198
|
+
OR octet_length(NEW.idempotency_key) NOT BETWEEN 1 AND 2048
|
|
199
|
+
OR (NEW.billing_provider_event_id IS NOT NULL
|
|
200
|
+
AND octet_length(NEW.billing_provider_event_id) > 2048) THEN
|
|
201
|
+
RAISE EXCEPTION 'usage event exceeds the enabled host-export wire bounds'
|
|
202
|
+
USING ERRCODE = '22001';
|
|
203
|
+
END IF;
|
|
204
|
+
|
|
205
|
+
IF NEW.turn_id IS NOT NULL THEN
|
|
206
|
+
SELECT
|
|
207
|
+
CASE WHEN octet_length(t.initiator_subject_id) <= 1024 THEN
|
|
208
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
209
|
+
'kind', t.initiator_kind,
|
|
210
|
+
'subjectId', t.initiator_subject_id,
|
|
211
|
+
'label', CASE
|
|
212
|
+
WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
|
|
213
|
+
THEN left(t.initiator_context ->> 'label', 256)
|
|
214
|
+
ELSE NULL
|
|
215
|
+
END
|
|
216
|
+
))
|
|
217
|
+
ELSE NULL END,
|
|
218
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
219
|
+
'label', CASE
|
|
220
|
+
WHEN jsonb_typeof(t.initiator_context -> 'label') = 'string'
|
|
221
|
+
THEN left(t.initiator_context ->> 'label', 256)
|
|
222
|
+
ELSE NULL
|
|
223
|
+
END,
|
|
224
|
+
'backfill', CASE
|
|
225
|
+
WHEN jsonb_typeof(t.initiator_context -> 'backfill') = 'boolean'
|
|
226
|
+
THEN t.initiator_context -> 'backfill'
|
|
227
|
+
ELSE NULL
|
|
228
|
+
END,
|
|
229
|
+
'attributionOmitted', CASE
|
|
230
|
+
WHEN octet_length(t.initiator_subject_id) > 1024 THEN 'subject_id_too_large'
|
|
231
|
+
ELSE NULL
|
|
232
|
+
END
|
|
233
|
+
)),
|
|
234
|
+
t.source
|
|
235
|
+
INTO v_initiator, v_context, v_origin
|
|
236
|
+
FROM %1$I.session_turns t
|
|
237
|
+
WHERE t.workspace_id = NEW.workspace_id AND t.id = NEW.turn_id;
|
|
238
|
+
ELSIF v_initiator IS NULL AND NEW.subject_id IS NOT NULL
|
|
239
|
+
AND octet_length(NEW.subject_id) <= 1024 THEN
|
|
240
|
+
v_initiator := jsonb_build_object('kind', 'subject', 'subjectId', NEW.subject_id);
|
|
241
|
+
END IF;
|
|
242
|
+
|
|
243
|
+
v_payload := jsonb_build_object(
|
|
244
|
+
'id', NEW.id,
|
|
245
|
+
'workspaceId', NEW.workspace_id,
|
|
246
|
+
'accountId', NEW.account_id,
|
|
247
|
+
'subjectId', NEW.subject_id,
|
|
248
|
+
'eventType', NEW.event_type,
|
|
249
|
+
'quantity', NEW.quantity,
|
|
250
|
+
'unit', NEW.unit,
|
|
251
|
+
'sourceResourceType', NEW.source_resource_type,
|
|
252
|
+
'sourceResourceId', NEW.source_resource_id,
|
|
253
|
+
'idempotencyKey', NEW.idempotency_key,
|
|
254
|
+
'occurredAt', NEW.occurred_at,
|
|
255
|
+
'recordedAt', NEW.recorded_at,
|
|
256
|
+
'exportedToBillingAt', NEW.exported_to_billing_at,
|
|
257
|
+
'billingProviderEventId', NEW.billing_provider_event_id
|
|
258
|
+
);
|
|
259
|
+
v_payload_bytes := octet_length(v_payload::text)
|
|
260
|
+
+ octet_length(coalesce(v_initiator, 'null'::jsonb)::text)
|
|
261
|
+
+ octet_length(v_context::text)
|
|
262
|
+
+ 768;
|
|
263
|
+
INSERT INTO %1$I.host_export_outbox (
|
|
264
|
+
export_kind, source_id, account_id, workspace_id, session_id,
|
|
265
|
+
turn_id, turn_attempt_id, event_type, idempotency_key, initiator,
|
|
266
|
+
initiator_context, origin, payload, envelope_bytes, occurred_at,
|
|
267
|
+
source_recorded_at, enqueued_at
|
|
268
|
+
) VALUES (
|
|
269
|
+
'usage_event', NEW.id, NEW.account_id, NEW.workspace_id, NEW.session_id,
|
|
270
|
+
NEW.turn_id, NEW.turn_attempt_id, NEW.event_type, NEW.idempotency_key,
|
|
271
|
+
v_initiator, coalesce(v_context, '{}'::jsonb), v_origin, v_payload,
|
|
272
|
+
greatest(1, v_payload_bytes), NEW.occurred_at,
|
|
273
|
+
NEW.recorded_at, clock_timestamp()
|
|
274
|
+
)
|
|
275
|
+
ON CONFLICT (export_kind, source_id) DO NOTHING;
|
|
276
|
+
RETURN NEW;
|
|
277
|
+
END $function$;
|
|
278
|
+
$create$, target_schema);
|
|
279
|
+
END $migration$;
|
|
280
|
+
|
|
281
|
+
REVOKE ALL ON FUNCTION
|
|
282
|
+
opengeni_private.enqueue_host_session_event_export() FROM PUBLIC;
|
|
283
|
+
REVOKE ALL ON FUNCTION
|
|
284
|
+
opengeni_private.enqueue_host_usage_event_export() FROM PUBLIC;
|
|
285
|
+
|
|
286
|
+
-- The old 0104 backfill resolved lineage from then-current session ancestry.
|
|
287
|
+
-- A session-scoped row enqueued before 0103's durable ledger boundary therefore
|
|
288
|
+
-- lacks immutable-capture provenance even when its root is non-null. Reject the
|
|
289
|
+
-- candidate without touching data. The single-row probe is bounded by the
|
|
290
|
+
-- migration statement timeout; operators must disposition suspect history
|
|
291
|
+
-- separately and rerun the same migration.
|
|
292
|
+
DO $migration$
|
|
293
|
+
DECLARE
|
|
294
|
+
capture_applied_at timestamptz;
|
|
295
|
+
capture_ledger_rows integer;
|
|
296
|
+
suspect_source_id uuid;
|
|
297
|
+
BEGIN
|
|
298
|
+
SELECT min(m.applied_at), count(*)::integer
|
|
299
|
+
INTO capture_applied_at, capture_ledger_rows
|
|
300
|
+
FROM "schema_migrations" m
|
|
301
|
+
WHERE m.name = '0103_host_export_root_session.sql';
|
|
302
|
+
|
|
303
|
+
IF capture_ledger_rows <> 1 OR capture_applied_at IS NULL THEN
|
|
304
|
+
RAISE EXCEPTION '0107 requires the exact 0103 schema-migration ledger boundary'
|
|
305
|
+
USING ERRCODE = '55000',
|
|
306
|
+
HINT = 'Apply migrations through immutable 0104 with the canonical runner before retrying 0107.';
|
|
307
|
+
END IF;
|
|
308
|
+
|
|
309
|
+
SELECT o.source_id
|
|
310
|
+
INTO suspect_source_id
|
|
311
|
+
FROM "host_export_outbox" o
|
|
312
|
+
WHERE o.session_id IS NOT NULL
|
|
313
|
+
AND (
|
|
314
|
+
o.root_session_id IS NULL
|
|
315
|
+
OR o.enqueued_at < capture_applied_at
|
|
316
|
+
)
|
|
317
|
+
ORDER BY o.enqueued_at, o.source_id
|
|
318
|
+
LIMIT 1;
|
|
319
|
+
|
|
320
|
+
IF suspect_source_id IS NOT NULL THEN
|
|
321
|
+
RAISE EXCEPTION '0107 found host-export lineage without immutable-capture provenance'
|
|
322
|
+
USING ERRCODE = '23514',
|
|
323
|
+
HINT = 'Do not derive lineage from current sessions; use an evidence-backed maintenance disposition, then retry 0107.';
|
|
324
|
+
END IF;
|
|
325
|
+
END $migration$;
|
|
326
|
+
|
|
327
|
+
DO $migration$
|
|
328
|
+
DECLARE
|
|
329
|
+
existing_expression text;
|
|
330
|
+
normalized_expression text;
|
|
331
|
+
BEGIN
|
|
332
|
+
SELECT pg_get_expr(c.conbin, c.conrelid, true)
|
|
333
|
+
INTO existing_expression
|
|
334
|
+
FROM pg_catalog.pg_constraint c
|
|
335
|
+
WHERE c.conname = 'host_export_outbox_root_session_check'
|
|
336
|
+
AND c.conrelid = 'host_export_outbox'::regclass
|
|
337
|
+
AND c.contype = 'c'
|
|
338
|
+
AND NOT c.connoinherit;
|
|
339
|
+
|
|
340
|
+
IF existing_expression IS NULL THEN
|
|
341
|
+
IF EXISTS (
|
|
342
|
+
SELECT 1
|
|
343
|
+
FROM pg_catalog.pg_constraint c
|
|
344
|
+
WHERE c.conname = 'host_export_outbox_root_session_check'
|
|
345
|
+
AND c.conrelid = 'host_export_outbox'::regclass
|
|
346
|
+
) THEN
|
|
347
|
+
RAISE EXCEPTION 'host_export_outbox_root_session_check has an incompatible constraint type'
|
|
348
|
+
USING ERRCODE = '55000';
|
|
349
|
+
END IF;
|
|
350
|
+
|
|
351
|
+
ALTER TABLE "host_export_outbox"
|
|
352
|
+
ADD CONSTRAINT "host_export_outbox_root_session_check"
|
|
353
|
+
CHECK ("session_id" IS NULL OR "root_session_id" IS NOT NULL)
|
|
354
|
+
NOT VALID;
|
|
355
|
+
ELSE
|
|
356
|
+
normalized_expression := lower(regexp_replace(
|
|
357
|
+
existing_expression,
|
|
358
|
+
'["()[:space:]]',
|
|
359
|
+
'',
|
|
360
|
+
'g'
|
|
361
|
+
));
|
|
362
|
+
IF normalized_expression <> 'session_idisnullorroot_session_idisnotnull' THEN
|
|
363
|
+
RAISE EXCEPTION 'host_export_outbox_root_session_check has an incompatible expression'
|
|
364
|
+
USING ERRCODE = '55000';
|
|
365
|
+
END IF;
|
|
366
|
+
END IF;
|
|
367
|
+
END $migration$;
|
|
368
|
+
|
|
369
|
+
-- VALIDATE takes SHARE UPDATE EXCLUSIVE, which permits ordinary reads/writes.
|
|
370
|
+
-- Both lock acquisition and the read-only table scan are source-bounded above;
|
|
371
|
+
-- timeout is a retryable operator disposition, never permission to rewrite data.
|
|
372
|
+
DO $migration$
|
|
373
|
+
BEGIN
|
|
374
|
+
ALTER TABLE "host_export_outbox"
|
|
375
|
+
VALIDATE CONSTRAINT "host_export_outbox_root_session_check";
|
|
376
|
+
EXCEPTION
|
|
377
|
+
WHEN check_violation THEN
|
|
378
|
+
RAISE EXCEPTION '0107 cannot validate host-export lineage without historical provenance'
|
|
379
|
+
USING ERRCODE = '23514',
|
|
380
|
+
HINT = 'Do not derive lineage from current sessions; use an evidence-backed maintenance disposition, then retry 0107.';
|
|
381
|
+
END $migration$;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Fence provider creates that outlive a failed or expired warming acquisition.
|
|
3
|
+
--
|
|
4
|
+
-- A warming row can be invalidated while a provider create is still
|
|
5
|
+
-- non-abortably in flight. Advancing lease_epoch in the same transaction that
|
|
6
|
+
-- exposes cold/draining means a successor can never reuse the old acquisition
|
|
7
|
+
-- epoch, so late record/commit/fail/cleanup callbacks fail their existing CAS.
|
|
8
|
+
|
|
9
|
+
CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
|
|
10
|
+
p_viewer_holder_ttl_ms bigint,
|
|
11
|
+
p_turn_holder_ttl_ms bigint,
|
|
12
|
+
p_idle_grace_ms bigint
|
|
13
|
+
)
|
|
14
|
+
RETURNS TABLE (workspace_id uuid, sandbox_group_id uuid, instance_id text, lease_epoch integer)
|
|
15
|
+
LANGUAGE plpgsql
|
|
16
|
+
SECURITY DEFINER
|
|
17
|
+
AS $$
|
|
18
|
+
BEGIN
|
|
19
|
+
DELETE FROM sandbox_lease_holders h
|
|
20
|
+
WHERE h.kind = 'viewer'
|
|
21
|
+
AND h.last_heartbeat_at < now() - make_interval(secs => p_viewer_holder_ttl_ms / 1000.0);
|
|
22
|
+
|
|
23
|
+
IF p_turn_holder_ttl_ms > 0 THEN
|
|
24
|
+
DELETE FROM sandbox_lease_holders h
|
|
25
|
+
WHERE h.kind = 'turn'
|
|
26
|
+
AND h.last_heartbeat_at < now() - make_interval(secs => p_turn_holder_ttl_ms / 1000.0);
|
|
27
|
+
END IF;
|
|
28
|
+
|
|
29
|
+
UPDATE sandbox_leases L SET
|
|
30
|
+
refcount = c.total,
|
|
31
|
+
turn_holders = c.turns,
|
|
32
|
+
viewer_holders = c.viewers,
|
|
33
|
+
liveness = CASE WHEN L.liveness = 'warm' AND c.total = 0 AND c.turns = 0
|
|
34
|
+
THEN 'draining' ELSE L.liveness END,
|
|
35
|
+
expires_at = CASE WHEN L.liveness = 'warm' AND c.total = 0 AND c.turns = 0
|
|
36
|
+
THEN now() + make_interval(secs => p_idle_grace_ms / 1000.0)
|
|
37
|
+
ELSE L.expires_at END,
|
|
38
|
+
updated_at = now()
|
|
39
|
+
FROM (
|
|
40
|
+
SELECT L2.id,
|
|
41
|
+
(SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id)::int AS total,
|
|
42
|
+
(SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id AND h.kind = 'turn')::int AS turns,
|
|
43
|
+
(SELECT count(*) FROM sandbox_lease_holders h WHERE h.lease_id = L2.id AND h.kind = 'viewer')::int AS viewers
|
|
44
|
+
FROM sandbox_leases L2
|
|
45
|
+
) c
|
|
46
|
+
WHERE L.id = c.id;
|
|
47
|
+
|
|
48
|
+
-- The old warming epoch is permanently closed before a successor can acquire.
|
|
49
|
+
UPDATE sandbox_leases AS L SET
|
|
50
|
+
liveness = 'cold', instance_id = NULL,
|
|
51
|
+
resume_backend_id = NULL, resume_state = NULL,
|
|
52
|
+
data_plane_url = NULL, terminal_data_plane_url = NULL,
|
|
53
|
+
lease_epoch = L.lease_epoch + 1,
|
|
54
|
+
updated_at = now()
|
|
55
|
+
WHERE L.liveness = 'warming' AND L.expires_at < now() AND L.instance_id IS NULL;
|
|
56
|
+
|
|
57
|
+
-- Keep an attributed provider id for the drain/terminate path, but fence the
|
|
58
|
+
-- expired creator before exposing the row as drainable.
|
|
59
|
+
UPDATE sandbox_leases AS L SET
|
|
60
|
+
liveness = 'draining',
|
|
61
|
+
refcount = 0,
|
|
62
|
+
turn_holders = 0,
|
|
63
|
+
viewer_holders = 0,
|
|
64
|
+
data_plane_url = NULL,
|
|
65
|
+
terminal_data_plane_url = NULL,
|
|
66
|
+
lease_epoch = L.lease_epoch + 1,
|
|
67
|
+
expires_at = now() - interval '1 millisecond',
|
|
68
|
+
updated_at = now()
|
|
69
|
+
WHERE L.liveness = 'warming' AND L.expires_at < now() AND L.instance_id IS NOT NULL;
|
|
70
|
+
|
|
71
|
+
RETURN QUERY
|
|
72
|
+
SELECT L.workspace_id, L.sandbox_group_id, L.instance_id, L.lease_epoch
|
|
73
|
+
FROM sandbox_leases L
|
|
74
|
+
WHERE L.liveness = 'draining' AND L.expires_at < now() AND L.refcount = 0;
|
|
75
|
+
END;
|
|
76
|
+
$$;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Expand phase: persist deployment policy and add nullable lineage columns.
|
|
3
|
+
SET lock_timeout = '5s';
|
|
4
|
+
SET statement_timeout = '30s';
|
|
5
|
+
|
|
6
|
+
CREATE TABLE "nested_agent_depth_configuration" (
|
|
7
|
+
"singleton" boolean PRIMARY KEY DEFAULT true NOT NULL,
|
|
8
|
+
"max_nested_agent_depth" integer NOT NULL,
|
|
9
|
+
"policy_source" text NOT NULL,
|
|
10
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
11
|
+
CONSTRAINT "nested_agent_depth_configuration_singleton_check" CHECK ("singleton"),
|
|
12
|
+
CONSTRAINT "nested_agent_depth_configuration_max_check"
|
|
13
|
+
CHECK ("max_nested_agent_depth" >= 0 AND "max_nested_agent_depth" <= 2147483647),
|
|
14
|
+
CONSTRAINT "nested_agent_depth_configuration_source_check"
|
|
15
|
+
CHECK ("policy_source" IN ('deployment', 'default'))
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
DO $grants$
|
|
19
|
+
BEGIN
|
|
20
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
21
|
+
EXECUTE format('GRANT SELECT ON %I."nested_agent_depth_configuration" TO opengeni_app', current_schema());
|
|
22
|
+
END IF;
|
|
23
|
+
END $grants$;
|
|
24
|
+
|
|
25
|
+
INSERT INTO "nested_agent_depth_configuration" (
|
|
26
|
+
"singleton", "max_nested_agent_depth", "policy_source"
|
|
27
|
+
) VALUES (
|
|
28
|
+
true,
|
|
29
|
+
coalesce(nullif(current_setting('opengeni.max_nested_agent_depth', true), ''), '3')::integer,
|
|
30
|
+
coalesce(nullif(current_setting('opengeni.nested_agent_depth_policy_source', true), ''), 'default')
|
|
31
|
+
) ON CONFLICT ("singleton") DO NOTHING;
|
|
32
|
+
|
|
33
|
+
ALTER TABLE "sessions"
|
|
34
|
+
ADD COLUMN IF NOT EXISTS "root_session_id" uuid,
|
|
35
|
+
ADD COLUMN IF NOT EXISTS "nested_agent_depth" integer,
|
|
36
|
+
ADD COLUMN IF NOT EXISTS "max_nested_agent_depth_override" integer,
|
|
37
|
+
ADD COLUMN IF NOT EXISTS "effective_max_nested_agent_depth" integer,
|
|
38
|
+
ADD COLUMN IF NOT EXISTS "nested_agent_depth_policy_source" text,
|
|
39
|
+
ADD COLUMN IF NOT EXISTS "nested_agent_depth_policy_session_id" uuid;
|
|
40
|
+
|
|
41
|
+
RESET statement_timeout;
|
|
42
|
+
RESET lock_timeout;
|