@opengeni/db 0.14.3 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/{chunk-Y5WZZVQK.js → chunk-SK7YYHBI.js} +5 -2
  2. package/dist/chunk-SK7YYHBI.js.map +1 -0
  3. package/dist/{chunk-ELMUCYZF.js → chunk-T6RSJT6C.js} +202 -43
  4. package/dist/chunk-T6RSJT6C.js.map +1 -0
  5. package/dist/chunk-TLAC622R.js +4990 -0
  6. package/dist/chunk-TLAC622R.js.map +1 -0
  7. package/dist/codex-token-resolver.d.ts +64 -0
  8. package/dist/connection-token-resolver.d.ts +90 -0
  9. package/dist/environment-crypto.d.ts +11 -0
  10. package/dist/event-payload-sanitizer.d.ts +32 -0
  11. package/dist/index.d.ts +6374 -10
  12. package/dist/index.js +8075 -2776
  13. package/dist/index.js.map +1 -1
  14. package/dist/insights.d.ts +157 -0
  15. package/dist/memory-domain.d.ts +44 -0
  16. package/dist/migrate.d.ts +5 -7
  17. package/dist/migrate.js +1 -1
  18. package/dist/new-session-drafts.d.ts +49 -0
  19. package/dist/persistence-errors.d.ts +69 -0
  20. package/dist/preference-registry-schema.d.ts +1147 -0
  21. package/dist/preference-registry.d.ts +878 -0
  22. package/dist/provision-roles.d.ts +4 -6489
  23. package/dist/provision-roles.js +1 -1
  24. package/dist/role-relationships.d.ts +35 -0
  25. package/dist/runtime-posture-cli.d.ts +1 -0
  26. package/dist/runtime-posture.d.ts +103 -0
  27. package/dist/schema.d.ts +25330 -3
  28. package/dist/schema.js +31 -1
  29. package/dist/session-control.d.ts +331 -0
  30. package/dist/session-queue-commands.d.ts +186 -0
  31. package/dist/session-tool-call-settlement.d.ts +32 -0
  32. package/dist/turn-initiator.d.ts +28 -0
  33. package/dist/workspace-artifacts.d.ts +62 -0
  34. package/dist/workspace-instruction-policies-schema.d.ts +735 -0
  35. package/dist/workspace-instruction-policies.d.ts +64 -0
  36. package/drizzle/0136_unified_session_tool_policy.sql +1 -1
  37. package/drizzle/0137_preference_registry.sql +1347 -0
  38. package/drizzle/0138_sandbox_checkpoint_artifacts_and_deadlines.sql +1074 -0
  39. package/drizzle/0139_codex_provider_artifact_invalidations.sql +51 -0
  40. package/drizzle/0140_sandbox_restore_and_reaper_fences.sql +276 -0
  41. package/drizzle/0142_sandbox_archive_capture_gate.sql +80 -0
  42. package/drizzle/0143_session_codex_compaction_mode.sql +20 -0
  43. package/drizzle/0144_sandbox_viewer_force_drain_gate.sql +95 -0
  44. package/drizzle/0145_model_call_facts.sql +96 -0
  45. package/drizzle/0146_slack_bot_delete_idempotency.sql +105 -0
  46. package/drizzle/0147_draft_latency_mode.sql +12 -0
  47. package/drizzle/0148_session_turn_latency_mode.sql +12 -0
  48. package/drizzle/0149_workspace_artifacts.sql +348 -0
  49. package/drizzle/0150_slack_task_interactions.sql +344 -0
  50. package/package.json +6 -6
  51. package/src/index.ts +5766 -275
  52. package/src/insights.ts +837 -0
  53. package/src/migrate.ts +9 -1
  54. package/src/new-session-drafts.ts +4 -0
  55. package/src/preference-registry-schema.ts +170 -0
  56. package/src/preference-registry.ts +1220 -0
  57. package/src/provision-roles.ts +95 -22
  58. package/src/role-relationships.ts +132 -0
  59. package/src/runtime-posture.ts +47 -27
  60. package/src/schema.ts +875 -19
  61. package/src/session-queue-commands.ts +39 -6
  62. package/src/workspace-artifacts.ts +751 -0
  63. package/dist/chunk-DW24CKCT.js +0 -4046
  64. package/dist/chunk-DW24CKCT.js.map +0 -1
  65. package/dist/chunk-ELMUCYZF.js.map +0 -1
  66. package/dist/chunk-Y5WZZVQK.js.map +0 -1
  67. package/dist/schema-DhkRhcuQ.d.ts +0 -22666
@@ -0,0 +1,51 @@
1
+ -- deployment-mode: rolling
2
+ -- Preserve durable conversation truth while allowing an exact Codex provider
3
+ -- rejection to invalidate only its opaque, provider-bound reasoning artifact.
4
+ -- The item itself remains immutable/auditable; the model read path omits the
5
+ -- rejected identity on the next fenced attempt.
6
+
7
+ ALTER TABLE "session_history_items"
8
+ ADD COLUMN "provider_artifact_invalidated_at" timestamptz,
9
+ ADD COLUMN "provider_artifact_invalidation_reason" text,
10
+ ADD COLUMN "provider_artifact_invalidated_by_attempt_id" uuid,
11
+ ADD CONSTRAINT "session_history_items_provider_artifact_invalidation_shape_chk"
12
+ CHECK (
13
+ (
14
+ "provider_artifact_invalidated_at" IS NULL
15
+ AND "provider_artifact_invalidation_reason" IS NULL
16
+ AND "provider_artifact_invalidated_by_attempt_id" IS NULL
17
+ )
18
+ OR
19
+ (
20
+ "provider_artifact_invalidated_at" IS NOT NULL
21
+ AND "provider_artifact_invalidation_reason" = 'encrypted_content_rejected'
22
+ AND "provider_artifact_invalidated_by_attempt_id" IS NOT NULL
23
+ AND "item" ->> 'type' IN ('reasoning', 'compaction')
24
+ )
25
+ ) NOT VALID;
26
+
27
+ ALTER TABLE "agent_run_states"
28
+ ADD COLUMN "provider_artifact_invalidated_at" timestamptz,
29
+ ADD COLUMN "provider_artifact_invalidation_reason" text,
30
+ ADD COLUMN "provider_artifact_invalidated_by_attempt_id" uuid,
31
+ ADD CONSTRAINT "agent_run_states_provider_artifact_invalidation_shape_chk"
32
+ CHECK (
33
+ (
34
+ "provider_artifact_invalidated_at" IS NULL
35
+ AND "provider_artifact_invalidation_reason" IS NULL
36
+ AND "provider_artifact_invalidated_by_attempt_id" IS NULL
37
+ )
38
+ OR
39
+ (
40
+ "provider_artifact_invalidated_at" IS NOT NULL
41
+ AND "provider_artifact_invalidation_reason" = 'encrypted_content_rejected'
42
+ AND "provider_artifact_invalidated_by_attempt_id" IS NOT NULL
43
+ )
44
+ ) NOT VALID;
45
+
46
+ DO $$
47
+ BEGIN
48
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
49
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO opengeni_app;
50
+ END IF;
51
+ END $$;
@@ -0,0 +1,276 @@
1
+ -- deployment-mode: rolling
2
+ -- A legacy Modal checkpoint is immutable at the generation at which it was
3
+ -- adopted. Restoring it and then writing to the live workspace must advance
4
+ -- workspace_generation without invalidating that checkpoint. Migration 0138
5
+ -- accidentally required archive_generation = workspace_generation forever,
6
+ -- so the first post-restore mutation failed the deferred scope trigger.
7
+ --
8
+ -- The global lease reaper also used one statement snapshot to count holders
9
+ -- before waiting on a concurrently acquired lease row. PostgreSQL could then
10
+ -- apply that stale zero count after the acquire committed, changing a live
11
+ -- lease to draining underneath its new owner. The replacement reaper locks a
12
+ -- bounded, fair batch with SKIP LOCKED first and touches only that exact batch.
13
+
14
+ DO $checkpoint_ref_validator$
15
+ DECLARE data_schema text := current_schema();
16
+ BEGIN
17
+ EXECUTE format($create$
18
+ CREATE OR REPLACE FUNCTION opengeni_private.validate_sandbox_checkpoint_refs()
19
+ RETURNS trigger
20
+ LANGUAGE plpgsql
21
+ SECURITY DEFINER
22
+ SET search_path = pg_catalog
23
+ AS $function$
24
+ DECLARE checkpoint %1$I.sandbox_checkpoint_artifacts%%ROWTYPE;
25
+ DECLARE legacy_archive_rebound boolean := false;
26
+ BEGIN
27
+ IF TG_OP = 'UPDATE' THEN
28
+ legacy_archive_rebound :=
29
+ OLD.current_checkpoint_artifact_id = NEW.current_checkpoint_artifact_id
30
+ AND OLD.archive_generation IS DISTINCT FROM NEW.archive_generation;
31
+ END IF;
32
+
33
+ IF NEW.current_checkpoint_artifact_id IS NOT NULL THEN
34
+ SELECT * INTO checkpoint
35
+ FROM %1$I.sandbox_checkpoint_artifacts
36
+ WHERE id = NEW.current_checkpoint_artifact_id;
37
+ IF NOT FOUND
38
+ OR checkpoint.account_id <> NEW.account_id
39
+ OR checkpoint.workspace_id <> NEW.workspace_id
40
+ OR checkpoint.sandbox_group_id <> NEW.sandbox_group_id
41
+ OR checkpoint.source_lease_id <> NEW.id
42
+ OR checkpoint.provider_backend <> NEW.backend
43
+ OR (
44
+ checkpoint.provenance = 'native_capture'
45
+ AND checkpoint.source_workspace_generation
46
+ IS DISTINCT FROM NEW.archive_generation
47
+ )
48
+ OR (
49
+ checkpoint.provenance = 'legacy_provider_adopted'
50
+ AND (
51
+ NEW.archive_generation IS NULL
52
+ OR legacy_archive_rebound
53
+ )
54
+ )
55
+ OR checkpoint.archive_base64
56
+ IS DISTINCT FROM NEW.resume_state #>> '{sessionState,workspaceArchive}'
57
+ OR checkpoint.descriptor_revision
58
+ IS DISTINCT FROM NEW.resume_state #>> '{sessionState,workspaceArchiveMeta,revision}'
59
+ OR checkpoint.state <> 'current'
60
+ THEN
61
+ RAISE EXCEPTION 'current checkpoint artifact does not match its exact lease scope'
62
+ USING ERRCODE = '23514';
63
+ END IF;
64
+ END IF;
65
+
66
+ IF NEW.previous_checkpoint_artifact_id IS NOT NULL THEN
67
+ SELECT * INTO checkpoint
68
+ FROM %1$I.sandbox_checkpoint_artifacts
69
+ WHERE id = NEW.previous_checkpoint_artifact_id;
70
+ IF NOT FOUND
71
+ OR checkpoint.account_id <> NEW.account_id
72
+ OR checkpoint.workspace_id <> NEW.workspace_id
73
+ OR checkpoint.sandbox_group_id <> NEW.sandbox_group_id
74
+ OR checkpoint.source_lease_id <> NEW.id
75
+ OR checkpoint.provider_backend <> NEW.backend
76
+ OR checkpoint.archive_base64
77
+ IS DISTINCT FROM NEW.resume_state #>> '{sessionState,workspaceArchivePrev}'
78
+ OR checkpoint.descriptor_revision
79
+ IS DISTINCT FROM NEW.resume_state #>> '{sessionState,workspaceArchivePrevMeta,revision}'
80
+ OR checkpoint.state <> 'previous'
81
+ THEN
82
+ RAISE EXCEPTION 'previous checkpoint artifact does not match its exact lease scope'
83
+ USING ERRCODE = '23514';
84
+ END IF;
85
+ END IF;
86
+ RETURN NEW;
87
+ END;
88
+ $function$;
89
+ $create$, data_schema);
90
+ END
91
+ $checkpoint_ref_validator$;
92
+
93
+ DO $bounded_lock_first_reaper$
94
+ DECLARE data_schema text := current_schema();
95
+ BEGIN
96
+ EXECUTE format($create$
97
+ CREATE OR REPLACE FUNCTION opengeni_private.reap_sandbox_leases(
98
+ p_viewer_holder_ttl_ms bigint,
99
+ p_turn_holder_ttl_ms bigint,
100
+ p_idle_grace_ms bigint
101
+ )
102
+ RETURNS TABLE (
103
+ workspace_id uuid,
104
+ sandbox_group_id uuid,
105
+ instance_id text,
106
+ lease_epoch integer
107
+ )
108
+ LANGUAGE plpgsql
109
+ SECURITY DEFINER
110
+ SET search_path = pg_catalog
111
+ AS $function$
112
+ DECLARE locked_ids uuid[];
113
+ BEGIN
114
+ PERFORM pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2', '1', true);
115
+
116
+ -- Every holder-creating runtime path locks its lease first. SKIP LOCKED
117
+ -- therefore gives one coherent statement snapshot without ever waiting
118
+ -- behind an acquire and later publishing pre-wait counts. Old/corrupt
119
+ -- cold rows carrying holders are included for bounded repair. Updating
120
+ -- selected rows' updated_at below rotates a backlog fairly.
121
+ SELECT coalesce(
122
+ pg_catalog.array_agg(candidate.id),
123
+ ARRAY[]::uuid[]
124
+ )
125
+ INTO locked_ids
126
+ FROM (
127
+ SELECT lease.id
128
+ FROM %1$I.sandbox_leases lease
129
+ WHERE lease.liveness <> 'cold'
130
+ OR EXISTS (
131
+ SELECT 1
132
+ FROM %1$I.sandbox_lease_holders holder
133
+ WHERE holder.lease_id = lease.id
134
+ )
135
+ ORDER BY lease.updated_at, lease.id
136
+ LIMIT 500
137
+ FOR UPDATE OF lease SKIP LOCKED
138
+ ) candidate;
139
+
140
+ IF pg_catalog.cardinality(locked_ids) = 0 THEN
141
+ RETURN;
142
+ END IF;
143
+
144
+ -- A legacy heartbeat/force-drain writer can still hold an individual
145
+ -- holder row during a rolling rollout. Lock stale victims with SKIP
146
+ -- LOCKED as well, avoiding a holder->lease / lease->holder deadlock.
147
+ DELETE FROM %1$I.sandbox_lease_holders holder
148
+ USING (
149
+ SELECT stale.id
150
+ FROM %1$I.sandbox_lease_holders stale
151
+ WHERE stale.lease_id = ANY(locked_ids)
152
+ AND stale.kind IN ('viewer', 'direct')
153
+ AND stale.last_heartbeat_at
154
+ < pg_catalog.now()
155
+ - pg_catalog.make_interval(secs => p_viewer_holder_ttl_ms / 1000.0)
156
+ FOR UPDATE OF stale SKIP LOCKED
157
+ ) victim
158
+ WHERE holder.id = victim.id;
159
+
160
+ IF p_turn_holder_ttl_ms > 0 THEN
161
+ DELETE FROM %1$I.sandbox_lease_holders holder
162
+ USING (
163
+ SELECT stale.id
164
+ FROM %1$I.sandbox_lease_holders stale
165
+ WHERE stale.lease_id = ANY(locked_ids)
166
+ AND stale.kind = 'turn'
167
+ AND stale.last_heartbeat_at
168
+ < pg_catalog.now()
169
+ - pg_catalog.make_interval(secs => p_turn_holder_ttl_ms / 1000.0)
170
+ FOR UPDATE OF stale SKIP LOCKED
171
+ ) victim
172
+ WHERE holder.id = victim.id;
173
+ END IF;
174
+
175
+ UPDATE %1$I.sandbox_leases lease SET
176
+ refcount = counts.total,
177
+ turn_holders = counts.turns,
178
+ viewer_holders = counts.viewers,
179
+ liveness = CASE
180
+ WHEN lease.liveness = 'warm' AND counts.total = 0 AND counts.turns = 0
181
+ THEN 'draining' ELSE lease.liveness END,
182
+ expires_at = CASE
183
+ WHEN lease.liveness = 'warm' AND counts.total = 0 AND counts.turns = 0
184
+ THEN CASE
185
+ WHEN lease.rotation_requested_at IS NOT NULL
186
+ THEN pg_catalog.now() - interval '1 millisecond'
187
+ ELSE pg_catalog.now()
188
+ + pg_catalog.make_interval(secs => p_idle_grace_ms / 1000.0)
189
+ END
190
+ ELSE lease.expires_at END,
191
+ updated_at = pg_catalog.now()
192
+ FROM (
193
+ SELECT candidate.id,
194
+ pg_catalog.count(holder.id)::int AS total,
195
+ pg_catalog.count(holder.id)
196
+ FILTER (WHERE holder.kind = 'turn')::int AS turns,
197
+ pg_catalog.count(holder.id)
198
+ FILTER (WHERE holder.kind = 'viewer')::int AS viewers
199
+ FROM pg_catalog.unnest(locked_ids) candidate(id)
200
+ LEFT JOIN %1$I.sandbox_lease_holders holder
201
+ ON holder.lease_id = candidate.id
202
+ GROUP BY candidate.id
203
+ ) counts
204
+ WHERE lease.id = counts.id;
205
+
206
+ UPDATE %1$I.sandbox_leases lease SET
207
+ liveness = 'cold',
208
+ instance_id = null,
209
+ lease_epoch = lease.lease_epoch + 1,
210
+ resume_state = opengeni_private.warming_reset_resume_state_v2(
211
+ lease.backend,
212
+ lease.resume_backend_id,
213
+ lease.resume_state,
214
+ lease.workspace_generation,
215
+ lease.archive_generation,
216
+ pg_catalog.clock_timestamp()
217
+ ),
218
+ resume_backend_id = CASE
219
+ WHEN coalesce(
220
+ lease.resume_state #>> '{sessionState,workspaceArchive}', ''
221
+ ) <> ''
222
+ OR coalesce(
223
+ lease.resume_state #>> '{sessionState,workspaceArchivePrev}', ''
224
+ ) <> ''
225
+ THEN coalesce(lease.resume_backend_id, lease.backend)
226
+ ELSE null END,
227
+ data_plane_url = null,
228
+ terminal_data_plane_url = null,
229
+ provider_created_at = null,
230
+ provider_deadline_at = null,
231
+ rotation_requested_at = null,
232
+ rotation_reason = null,
233
+ updated_at = pg_catalog.now()
234
+ WHERE lease.id = ANY(locked_ids)
235
+ AND lease.liveness = 'warming'
236
+ AND lease.expires_at < pg_catalog.now()
237
+ AND lease.instance_id IS NULL;
238
+
239
+ UPDATE %1$I.sandbox_leases lease SET
240
+ liveness = 'draining',
241
+ refcount = 0,
242
+ turn_holders = 0,
243
+ viewer_holders = 0,
244
+ data_plane_url = null,
245
+ terminal_data_plane_url = null,
246
+ lease_epoch = lease.lease_epoch + 1,
247
+ expires_at = pg_catalog.now() - interval '1 millisecond',
248
+ updated_at = pg_catalog.now()
249
+ WHERE lease.id = ANY(locked_ids)
250
+ AND lease.liveness = 'warming'
251
+ AND lease.expires_at < pg_catalog.now()
252
+ AND lease.instance_id IS NOT NULL;
253
+
254
+ RETURN QUERY
255
+ SELECT lease.workspace_id, lease.sandbox_group_id,
256
+ lease.instance_id, lease.lease_epoch
257
+ FROM %1$I.sandbox_leases lease
258
+ WHERE lease.id = ANY(locked_ids)
259
+ AND lease.liveness = 'draining'
260
+ AND lease.expires_at < pg_catalog.now()
261
+ AND lease.refcount = 0;
262
+ END;
263
+ $function$;
264
+ $create$, data_schema);
265
+ END
266
+ $bounded_lock_first_reaper$;
267
+
268
+ DO $grants$
269
+ BEGIN
270
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
271
+ GRANT EXECUTE ON FUNCTION
272
+ opengeni_private.reap_sandbox_leases(bigint, bigint, bigint)
273
+ TO opengeni_app;
274
+ END IF;
275
+ END
276
+ $grants$;
@@ -0,0 +1,80 @@
1
+ -- deployment-mode: maintenance
2
+ -- A provider-native workspace snapshot pauses the Modal container while it is
3
+ -- captured. The previous generation preflight prevented a stale snapshot from
4
+ -- being published, but it did not prevent a new provider command from being
5
+ -- admitted after preflight and rejected by Modal while the container was
6
+ -- paused. Activate one durable lease-local capture gate that both snapshot
7
+ -- paths claim before provider I/O and every holder/mutation path honors.
8
+ --
9
+ -- Old application workers do not honor this gate. Stop every opengeni_app
10
+ -- session before activation and never restart a pre-0142 image afterward.
11
+
12
+ SET LOCAL lock_timeout = '5s';
13
+ SET LOCAL statement_timeout = '30min';
14
+
15
+ DO $maintenance_preflight_guard$
16
+ BEGIN
17
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
18
+ AND EXISTS (
19
+ SELECT 1
20
+ FROM pg_stat_activity
21
+ WHERE datname = current_database()
22
+ AND usename = 'opengeni_app'
23
+ AND pid <> pg_backend_pid()
24
+ )
25
+ THEN
26
+ RAISE EXCEPTION
27
+ 'sandbox archive-capture gate activation requires all opengeni_app sessions to be stopped'
28
+ USING ERRCODE = '55000';
29
+ END IF;
30
+ END
31
+ $maintenance_preflight_guard$;
32
+
33
+ LOCK TABLE sandbox_leases IN ACCESS EXCLUSIVE MODE;
34
+
35
+ DO $maintenance_guard$
36
+ BEGIN
37
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
38
+ AND EXISTS (
39
+ SELECT 1
40
+ FROM pg_stat_activity
41
+ WHERE datname = current_database()
42
+ AND usename = 'opengeni_app'
43
+ AND pid <> pg_backend_pid()
44
+ )
45
+ THEN
46
+ RAISE EXCEPTION
47
+ 'sandbox archive-capture gate activation requires all opengeni_app sessions to be stopped'
48
+ USING ERRCODE = '55000';
49
+ END IF;
50
+ END
51
+ $maintenance_guard$;
52
+
53
+ ALTER TABLE sandbox_leases
54
+ ADD COLUMN archive_capture_id uuid,
55
+ ADD COLUMN archive_capture_generation integer,
56
+ ADD COLUMN archive_capture_started_at timestamptz,
57
+ ADD COLUMN archive_capture_deadline_at timestamptz;
58
+
59
+ ALTER TABLE sandbox_leases
60
+ ADD CONSTRAINT sandbox_leases_archive_capture_check
61
+ CHECK (
62
+ (
63
+ archive_capture_id IS NULL
64
+ AND archive_capture_generation IS NULL
65
+ AND archive_capture_started_at IS NULL
66
+ AND archive_capture_deadline_at IS NULL
67
+ )
68
+ OR
69
+ (
70
+ archive_capture_id IS NOT NULL
71
+ AND archive_capture_generation IS NOT NULL
72
+ AND archive_capture_generation = workspace_generation
73
+ AND archive_capture_started_at IS NOT NULL
74
+ AND archive_capture_deadline_at > archive_capture_started_at
75
+ )
76
+ );
77
+
78
+ CREATE INDEX sandbox_leases_archive_capture_deadline_idx
79
+ ON sandbox_leases (archive_capture_deadline_at, id)
80
+ WHERE archive_capture_id IS NOT NULL;
@@ -0,0 +1,20 @@
1
+ -- deployment-mode: rolling
2
+ -- Freeze each session's Codex compaction strategy at create time.
3
+ -- Existing sessions stay on portable plaintext compaction so mid-session
4
+ -- provider switches remain available; new Codex sessions default to remote_v2
5
+ -- unless the workspace opts into portable.
6
+
7
+ ALTER TABLE "sessions"
8
+ ADD COLUMN IF NOT EXISTS "codex_compaction_mode" text NOT NULL DEFAULT 'portable';
9
+
10
+ ALTER TABLE "sessions"
11
+ DROP CONSTRAINT IF EXISTS "sessions_codex_compaction_mode_chk";
12
+
13
+ ALTER TABLE "sessions"
14
+ ADD CONSTRAINT "sessions_codex_compaction_mode_chk"
15
+ CHECK ("codex_compaction_mode" IN ('remote_v2', 'portable'));
16
+
17
+ UPDATE "sessions"
18
+ SET "codex_compaction_mode" = 'portable'
19
+ WHERE "codex_compaction_mode" IS DISTINCT FROM 'portable'
20
+ AND "codex_compaction_mode" IS DISTINCT FROM 'remote_v2';
@@ -0,0 +1,95 @@
1
+ -- deployment-mode: maintenance
2
+ -- A workspace that has exhausted its managed-credit balance or monthly warm
3
+ -- allowance must not let an open dashboard immediately re-arm a viewer-only
4
+ -- sandbox during the drain grace. Persist that admission intent independently
5
+ -- of any one lease so it survives provider teardown and a cold successor row.
6
+
7
+ SET LOCAL lock_timeout = '5s';
8
+ -- Old application processes do not honor this new workspace gate. Activate it
9
+ -- only while the maintenance operator has stopped every opengeni_app session.
10
+ SET LOCAL statement_timeout = '30min';
11
+
12
+ DO $maintenance_preflight_guard$
13
+ BEGIN
14
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
15
+ AND EXISTS (
16
+ SELECT 1
17
+ FROM pg_stat_activity
18
+ WHERE datname = current_database()
19
+ AND usename = 'opengeni_app'
20
+ AND pid <> pg_backend_pid()
21
+ )
22
+ THEN
23
+ RAISE EXCEPTION
24
+ 'sandbox viewer force-drain gate activation requires all opengeni_app sessions to be stopped'
25
+ USING ERRCODE = '55000';
26
+ END IF;
27
+ END
28
+ $maintenance_preflight_guard$;
29
+
30
+ LOCK TABLE workspaces IN ACCESS EXCLUSIVE MODE;
31
+
32
+ DO $maintenance_guard$
33
+ BEGIN
34
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
35
+ AND EXISTS (
36
+ SELECT 1
37
+ FROM pg_stat_activity
38
+ WHERE datname = current_database()
39
+ AND usename = 'opengeni_app'
40
+ AND pid <> pg_backend_pid()
41
+ )
42
+ THEN
43
+ RAISE EXCEPTION
44
+ 'sandbox viewer force-drain gate activation requires all opengeni_app sessions to be stopped'
45
+ USING ERRCODE = '55000';
46
+ END IF;
47
+ END
48
+ $maintenance_guard$;
49
+
50
+ ALTER TABLE workspaces
51
+ ADD COLUMN sandbox_viewer_force_drain_reason text,
52
+ ADD COLUMN sandbox_viewer_force_drain_requested_at timestamptz;
53
+
54
+ ALTER TABLE workspaces
55
+ ADD CONSTRAINT workspaces_sandbox_viewer_force_drain_check
56
+ CHECK (
57
+ (
58
+ sandbox_viewer_force_drain_reason IS NULL
59
+ AND sandbox_viewer_force_drain_requested_at IS NULL
60
+ )
61
+ OR
62
+ (
63
+ sandbox_viewer_force_drain_reason IN ('balance', 'warm_cap')
64
+ AND sandbox_viewer_force_drain_requested_at IS NOT NULL
65
+ )
66
+ );
67
+
68
+ CREATE INDEX workspaces_sandbox_viewer_force_drain_idx
69
+ ON workspaces (id)
70
+ WHERE sandbox_viewer_force_drain_reason IS NOT NULL;
71
+
72
+ -- The global sandbox reaper must keep reevaluating a persisted admission gate
73
+ -- after every affected lease becomes cold. Returning workspace identity only
74
+ -- keeps billing values and tenant data behind their existing scoped reads.
75
+ CREATE OR REPLACE FUNCTION opengeni_private.list_sandbox_viewer_force_drain_workspaces()
76
+ RETURNS TABLE (workspace_id uuid)
77
+ LANGUAGE sql
78
+ SECURITY DEFINER
79
+ -- EMBED-SAFE: inherit the caller's target-schema search_path, matching the
80
+ -- existing cross-workspace sandbox inventory functions.
81
+ AS $$
82
+ SELECT W.id
83
+ FROM workspaces W
84
+ WHERE W.sandbox_viewer_force_drain_reason IS NOT NULL
85
+ ORDER BY W.id;
86
+ $$;
87
+
88
+ DO $$
89
+ BEGIN
90
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
91
+ GRANT EXECUTE
92
+ ON FUNCTION opengeni_private.list_sandbox_viewer_force_drain_workspaces()
93
+ TO opengeni_app;
94
+ END IF;
95
+ END $$;
@@ -0,0 +1,96 @@
1
+ -- deployment-mode: rolling
2
+ -- Additive Workspace Insights fact table for per-model-call tokens/cache/provider
3
+ -- pivots. Written only after an authoritative agent.model.usage emit; never widens
4
+ -- the billing usage_events ledger. FORCE RLS matches usage_events isolation.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ CREATE TABLE IF NOT EXISTS "model_call_facts" (
10
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
11
+ "account_id" uuid NOT NULL,
12
+ "workspace_id" uuid NOT NULL,
13
+ "session_id" uuid NOT NULL,
14
+ "turn_id" uuid NOT NULL,
15
+ "turn_attempt_id" uuid,
16
+ "source_key" text NOT NULL,
17
+ "provider" text NOT NULL,
18
+ "provider_api" text NOT NULL,
19
+ "model" text NOT NULL,
20
+ "billing_path" text NOT NULL,
21
+ "turn_source" text,
22
+ "initiator_kind" text,
23
+ "initiator_subject_id" text,
24
+ "scheduled_task_id" uuid,
25
+ "input_tokens" bigint,
26
+ "output_tokens" bigint,
27
+ "cached_tokens" bigint,
28
+ "cache_write_tokens" bigint,
29
+ "reasoning_tokens" bigint,
30
+ "total_tokens" bigint,
31
+ "priced_cost_micros" bigint NOT NULL DEFAULT 0,
32
+ "occurred_at" timestamptz NOT NULL,
33
+ "recorded_at" timestamptz NOT NULL DEFAULT now(),
34
+ CONSTRAINT "model_call_facts_workspace_account_fk"
35
+ FOREIGN KEY ("workspace_id", "account_id")
36
+ REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
37
+ CONSTRAINT "model_call_facts_billing_path_check"
38
+ CHECK ("billing_path" IN ('opengeni_credits', 'external')),
39
+ CONSTRAINT "model_call_facts_priced_cost_check"
40
+ CHECK ("priced_cost_micros" >= 0),
41
+ CONSTRAINT "model_call_facts_initiator_check"
42
+ CHECK (
43
+ ("initiator_kind" IS NULL AND "initiator_subject_id" IS NULL)
44
+ OR (
45
+ "initiator_kind" IN ('subject', 'service')
46
+ AND "initiator_subject_id" IS NOT NULL
47
+ AND octet_length("initiator_subject_id") BETWEEN 1 AND 1024
48
+ )
49
+ ),
50
+ CONSTRAINT "model_call_facts_source_key_bytes_check"
51
+ CHECK (octet_length("source_key") BETWEEN 1 AND 1024),
52
+ CONSTRAINT "model_call_facts_provider_bytes_check"
53
+ CHECK (octet_length("provider") BETWEEN 1 AND 256),
54
+ CONSTRAINT "model_call_facts_provider_api_bytes_check"
55
+ CHECK (octet_length("provider_api") BETWEEN 1 AND 256),
56
+ CONSTRAINT "model_call_facts_model_bytes_check"
57
+ CHECK (octet_length("model") BETWEEN 1 AND 512)
58
+ );
59
+
60
+ CREATE UNIQUE INDEX IF NOT EXISTS "model_call_facts_workspace_turn_source_uq"
61
+ ON "model_call_facts" ("workspace_id", "turn_id", "source_key");
62
+
63
+ CREATE INDEX IF NOT EXISTS "model_call_facts_workspace_occurred_idx"
64
+ ON "model_call_facts" ("workspace_id", "occurred_at");
65
+
66
+ CREATE INDEX IF NOT EXISTS "model_call_facts_workspace_provider_model_occurred_idx"
67
+ ON "model_call_facts" ("workspace_id", "provider", "model", "occurred_at");
68
+
69
+ CREATE INDEX IF NOT EXISTS "model_call_facts_workspace_session_occurred_idx"
70
+ ON "model_call_facts" ("workspace_id", "session_id", "occurred_at");
71
+
72
+ CREATE INDEX IF NOT EXISTS "model_call_facts_workspace_scheduled_task_occurred_idx"
73
+ ON "model_call_facts" ("workspace_id", "scheduled_task_id", "occurred_at")
74
+ WHERE "scheduled_task_id" IS NOT NULL;
75
+
76
+ ALTER TABLE "model_call_facts" ENABLE ROW LEVEL SECURITY;
77
+ ALTER TABLE "model_call_facts" FORCE ROW LEVEL SECURITY;
78
+
79
+ DROP POLICY IF EXISTS "model_call_facts_account_workspace_isolation" ON "model_call_facts";
80
+ CREATE POLICY "model_call_facts_account_workspace_isolation" ON "model_call_facts"
81
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
82
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
83
+
84
+ DO $grants$
85
+ DECLARE target_schema text := current_schema();
86
+ BEGIN
87
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
88
+ EXECUTE format(
89
+ 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.model_call_facts TO opengeni_app',
90
+ target_schema
91
+ );
92
+ END IF;
93
+ END $grants$;
94
+
95
+ RESET statement_timeout;
96
+ RESET lock_timeout;