@opengeni/db 0.7.2 → 0.9.3
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-B22X3IEZ.js → chunk-4LG5NBTC.js} +512 -32
- package/dist/chunk-4LG5NBTC.js.map +1 -0
- package/dist/{chunk-YFQ7SGE4.js → chunk-BMFDXFPA.js} +23 -1
- package/dist/chunk-BMFDXFPA.js.map +1 -0
- package/dist/chunk-KW526IJA.js +127 -0
- package/dist/chunk-KW526IJA.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6092 -2080
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +29 -4
- package/dist/migrate.js +3 -1
- package/dist/provision-roles.d.ts +732 -47
- package/dist/provision-roles.js +1 -1
- package/dist/{schema-BN5mB9xZ.d.ts → schema-CdPGTHlD.d.ts} +2118 -119
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +17 -1
- package/drizzle/0064_rotation_strategy_sharded_backfill.sql +15 -0
- package/drizzle/0065_session_attempt_quiescence.sql +26 -0
- package/drizzle/0066_session_interruption_attempt_lookup.sql +4 -0
- package/drizzle/0067_session_event_payload_bounds.sql +209 -0
- package/drizzle/0068_workspace_control_event_bounds.sql +134 -0
- package/drizzle/0069_session_event_history_backfill.sql +32 -0
- package/drizzle/0070_session_event_type_sequence_lookup.sql +4 -0
- package/drizzle/0071_session_event_monitoring_tail.sql +10 -0
- package/drizzle/0072_sessions_workspace_created_id_idx.sql +4 -0
- package/drizzle/0073_sessions_workspace_updated_id_idx.sql +4 -0
- package/drizzle/0074_session_activity_revisions.sql +72 -0
- package/drizzle/0075_sessions_workspace_activity_revision_idx.sql +4 -0
- package/drizzle/0076_session_workflow_wake_acl.sql +13 -0
- package/drizzle/0077_session_attempt_latest_lookup.sql +4 -0
- package/drizzle/0094_quarantine_credential_bearing_catalog_urls.sql +51 -0
- package/drizzle/0095_github_existing_installations.sql +84 -0
- package/drizzle/0096_session_turn_initiators.sql +97 -0
- package/drizzle/0097_host_export_outbox.sql +1220 -0
- package/drizzle/0098_usage_events_workspace_session_idx.sql +4 -0
- package/drizzle/0099_session_human_input_attempt_owner_index.sql +6 -0
- package/drizzle/0100_session_human_input_requests.sql +89 -0
- package/drizzle/0101_session_mcp_connection_refs.sql +30 -0
- package/drizzle/0102_session_command_receipt_service_actor.sql +16 -0
- package/drizzle/0103_host_export_root_session.sql +166 -0
- package/drizzle/0104_host_export_root_session_backfill.sql +27 -0
- package/drizzle/0105_session_turn_instructions.sql +9 -0
- package/package.json +4 -4
- package/src/connection-token-resolver.ts +287 -1
- package/src/event-payload-sanitizer.ts +57 -19
- package/src/index.ts +5429 -1131
- package/src/memory-domain.ts +1 -1
- package/src/migrate.ts +86 -23
- package/src/persistence-errors.ts +252 -0
- package/src/provision-roles.ts +42 -0
- package/src/schema.ts +552 -34
- package/src/session-control.ts +518 -38
- package/src/session-queue-commands.ts +308 -57
- package/src/session-tool-call-settlement.ts +58 -7
- package/src/turn-initiator.ts +155 -0
- package/dist/chunk-7LDU7F5P.js +0 -80
- package/dist/chunk-7LDU7F5P.js.map +0 -1
- package/dist/chunk-B22X3IEZ.js.map +0 -1
- package/dist/chunk-YFQ7SGE4.js.map +0 -1
package/dist/schema.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'drizzle-orm';
|
|
2
2
|
import 'drizzle-orm/pg-core';
|
|
3
|
-
export {
|
|
3
|
+
export { n as agentRunStates, o as apiKeys, p as auditEvents, q as billingCustomers, r as capabilityCatalogItems, t as capabilityInstallations, u as codexCapacityWaiters, v as codexCredentialLeases, x as codexRotationSettings, y as codexSubscriptionCredentials, d as composerDrafts, z as connections, A as creditLedgerEntries, B as deviceEnrollmentRequests, h as deviceEnrollmentStatusValues, C as documentBases, D as documentChunks, E as documents, g as enrollmentExposureValues, f as enrollmentOsValues, i as enrollmentStatusValues, F as enrollments, G as fileUploads, H as files, I as githubInstallationRepositories, J as githubInstallations, K as hostExportConfig, L as hostExportConsumers, M as hostExportCursorState, N as hostExportDeadLetters, O as hostExportOutbox, P as importBatches, Q as integrationOauthClients, R as integrationOauthStateNonces, S as knowledgeMemories, T as machineMetricsLatest, U as machineMetricsSeries, V as managedAccounts, W as packInstallations, X as rigChanges, Y as rigVersions, Z as rigs, j as sandboxKindValues, _ as sandboxLeaseHolders, $ as sandboxLeaseLivenessValues, a0 as sandboxLeases, a1 as sandboxPtySessions, a2 as sandboxSessionEnvelopes, a3 as sandboxes, a4 as scheduledTaskRuns, a5 as scheduledTasks, a6 as sessionAttemptInterruptions, s as sessionCommandReceipts, a7 as sessionEvents, a8 as sessionGoals, a9 as sessionHistoryItems, aa as sessionHumanInputRequests, ab as sessionListSnapshots, ac as sessionMcpServers, ad as sessionPendingToolCalls, ae as sessionPins, k as sessionRecordingCodecValues, l as sessionRecordingModeValues, m as sessionRecordingStateValues, af as sessionRecordings, ag as sessionSystemUpdateOutbox, ah as sessionSystemUpdates, c as sessionTurnAttempts, b as sessionTurns, ai as sessionWorkflowWakeOutbox, a as sessions, aj as socialConnections, ak as socialPosts, al as stripeWebhookEvents, am as usageEvents, an as workspaceCaptures, ao as workspaceControlEvents, ap as workspaceInferenceControls, aq as workspaceMemberships, ar as workspaceModelPolicies, as as workspacePacks, at as workspaceSessionActivityRevisions, au as workspaceVariableSetVariables, av as workspaceVariableSets, w as workspaces } from './schema-CdPGTHlD.js';
|
package/dist/schema.js
CHANGED
|
@@ -23,7 +23,13 @@ import {
|
|
|
23
23
|
enrollments,
|
|
24
24
|
fileUploads,
|
|
25
25
|
files,
|
|
26
|
+
githubInstallationRepositories,
|
|
26
27
|
githubInstallations,
|
|
28
|
+
hostExportConfig,
|
|
29
|
+
hostExportConsumers,
|
|
30
|
+
hostExportCursorState,
|
|
31
|
+
hostExportDeadLetters,
|
|
32
|
+
hostExportOutbox,
|
|
27
33
|
importBatches,
|
|
28
34
|
integrationOauthClients,
|
|
29
35
|
integrationOauthStateNonces,
|
|
@@ -49,6 +55,7 @@ import {
|
|
|
49
55
|
sessionEvents,
|
|
50
56
|
sessionGoals,
|
|
51
57
|
sessionHistoryItems,
|
|
58
|
+
sessionHumanInputRequests,
|
|
52
59
|
sessionListSnapshots,
|
|
53
60
|
sessionMcpServers,
|
|
54
61
|
sessionPendingToolCalls,
|
|
@@ -73,10 +80,11 @@ import {
|
|
|
73
80
|
workspaceMemberships,
|
|
74
81
|
workspaceModelPolicies,
|
|
75
82
|
workspacePacks,
|
|
83
|
+
workspaceSessionActivityRevisions,
|
|
76
84
|
workspaceVariableSetVariables,
|
|
77
85
|
workspaceVariableSets,
|
|
78
86
|
workspaces
|
|
79
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-4LG5NBTC.js";
|
|
80
88
|
import "./chunk-PZ5AY32C.js";
|
|
81
89
|
export {
|
|
82
90
|
agentRunStates,
|
|
@@ -103,7 +111,13 @@ export {
|
|
|
103
111
|
enrollments,
|
|
104
112
|
fileUploads,
|
|
105
113
|
files,
|
|
114
|
+
githubInstallationRepositories,
|
|
106
115
|
githubInstallations,
|
|
116
|
+
hostExportConfig,
|
|
117
|
+
hostExportConsumers,
|
|
118
|
+
hostExportCursorState,
|
|
119
|
+
hostExportDeadLetters,
|
|
120
|
+
hostExportOutbox,
|
|
107
121
|
importBatches,
|
|
108
122
|
integrationOauthClients,
|
|
109
123
|
integrationOauthStateNonces,
|
|
@@ -129,6 +143,7 @@ export {
|
|
|
129
143
|
sessionEvents,
|
|
130
144
|
sessionGoals,
|
|
131
145
|
sessionHistoryItems,
|
|
146
|
+
sessionHumanInputRequests,
|
|
132
147
|
sessionListSnapshots,
|
|
133
148
|
sessionMcpServers,
|
|
134
149
|
sessionPendingToolCalls,
|
|
@@ -153,6 +168,7 @@ export {
|
|
|
153
168
|
workspaceMemberships,
|
|
154
169
|
workspaceModelPolicies,
|
|
155
170
|
workspacePacks,
|
|
171
|
+
workspaceSessionActivityRevisions,
|
|
156
172
|
workspaceVariableSetVariables,
|
|
157
173
|
workspaceVariableSets,
|
|
158
174
|
workspaces
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- OPE-36: the strategy picker is gone — rotation-enabled always behaves as
|
|
3
|
+
-- sticky-sharded (worker-side effectiveRotationStrategy normalizes every read).
|
|
4
|
+
-- Backfill stored legacy values so the column tells the truth going forward.
|
|
5
|
+
-- The column itself is KEPT (old-binary rollback safety): an old worker reading
|
|
6
|
+
-- 'sharded' runs the strategy that was already live on every real workspace.
|
|
7
|
+
|
|
8
|
+
SET lock_timeout = '5s';
|
|
9
|
+
SET statement_timeout = '10min';
|
|
10
|
+
|
|
11
|
+
UPDATE "codex_rotation_settings"
|
|
12
|
+
SET "rotation_strategy" = 'sharded'
|
|
13
|
+
WHERE "rotation_strategy" IS DISTINCT FROM 'sharded';
|
|
14
|
+
|
|
15
|
+
ALTER TABLE "codex_rotation_settings" ALTER COLUMN "rotation_strategy" SET DEFAULT 'sharded';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- A cancelled attempt records the exact boundary after which it has no
|
|
3
|
+
-- inference, user-visible output, or workspace-persistence authority.
|
|
4
|
+
-- Fenced/idempotent cleanup and telemetry may still finish. Temporal
|
|
5
|
+
-- independently waits for the activity to terminate; this receipt lets queue
|
|
6
|
+
-- admission and UI state use durable truth.
|
|
7
|
+
|
|
8
|
+
SET lock_timeout = '5s';
|
|
9
|
+
SET statement_timeout = '5min';
|
|
10
|
+
|
|
11
|
+
ALTER TABLE "session_turn_attempts"
|
|
12
|
+
ADD COLUMN "quiesced_at" timestamptz;
|
|
13
|
+
|
|
14
|
+
-- Maintenance mode drains the pre-cutover workers before this backfill, so an
|
|
15
|
+
-- interrupted attempt that is already closed has also left its physical
|
|
16
|
+
-- activity. Seed only that historical population; new activity/workflow code
|
|
17
|
+
-- owns all later receipts.
|
|
18
|
+
UPDATE "session_turn_attempts" AS attempt
|
|
19
|
+
SET "quiesced_at" = COALESCE(attempt."closed_at", attempt."updated_at", now())
|
|
20
|
+
WHERE attempt."state" = 'closed'
|
|
21
|
+
AND EXISTS (
|
|
22
|
+
SELECT 1
|
|
23
|
+
FROM "session_attempt_interruptions" AS interruption
|
|
24
|
+
WHERE interruption."workspace_id" = attempt."workspace_id"
|
|
25
|
+
AND interruption."attempt_id" = attempt."id"
|
|
26
|
+
);
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- OPE-64: session_events is the lossy human/audit projection, not a blob store.
|
|
3
|
+
-- Install the BEFORE trigger before the NOT VALID check so an older pod in the
|
|
4
|
+
-- same rolling deployment is normalized instead of having an oversized write
|
|
5
|
+
-- rejected. Historical oversized rows stay readable and can be defensively
|
|
6
|
+
-- bounded by NATS/SSE; NOT VALID enforces the invariant for every new row.
|
|
7
|
+
|
|
8
|
+
SET lock_timeout = '5s';
|
|
9
|
+
SET statement_timeout = '10min';
|
|
10
|
+
|
|
11
|
+
CREATE OR REPLACE FUNCTION opengeni_private.project_session_event_payload(source_payload jsonb)
|
|
12
|
+
RETURNS jsonb
|
|
13
|
+
LANGUAGE plpgsql
|
|
14
|
+
IMMUTABLE
|
|
15
|
+
SET search_path = pg_catalog
|
|
16
|
+
AS $function$
|
|
17
|
+
DECLARE
|
|
18
|
+
original_bytes integer;
|
|
19
|
+
delivered_bytes integer;
|
|
20
|
+
bounded jsonb;
|
|
21
|
+
BEGIN
|
|
22
|
+
original_bytes := octet_length(source_payload::text);
|
|
23
|
+
IF original_bytes <= 65536 THEN
|
|
24
|
+
RETURN source_payload;
|
|
25
|
+
END IF;
|
|
26
|
+
|
|
27
|
+
-- This is intentionally a small content-free fallback. Normal application
|
|
28
|
+
-- writers produce richer deterministic head/tail previews before INSERT; the
|
|
29
|
+
-- trigger exists for rolling old binaries and unforeseen direct writers.
|
|
30
|
+
bounded := jsonb_strip_nulls(jsonb_build_object(
|
|
31
|
+
'id', left(source_payload ->> 'id', 256),
|
|
32
|
+
'name', left(coalesce(source_payload ->> 'name', source_payload ->> 'toolName'), 256),
|
|
33
|
+
'type', left(source_payload ->> 'type', 128),
|
|
34
|
+
'status', left(source_payload ->> 'status', 128),
|
|
35
|
+
'code', left(source_payload ->> 'code', 128),
|
|
36
|
+
'isError', source_payload -> 'isError',
|
|
37
|
+
'preview', '[event payload omitted by durable audit storage guard]',
|
|
38
|
+
'truncation', jsonb_build_object(
|
|
39
|
+
'truncated', true,
|
|
40
|
+
'surface', 'database_guard',
|
|
41
|
+
'reason', 'database_guard',
|
|
42
|
+
'originalBytes', original_bytes,
|
|
43
|
+
'deliveredBytes', 0,
|
|
44
|
+
'omittedBytes', original_bytes,
|
|
45
|
+
'estimatedOriginalTokens', ceil(original_bytes / 4.0)::integer,
|
|
46
|
+
'estimatedDeliveredTokens', 0,
|
|
47
|
+
'fullEvidence', jsonb_build_object('available', false, 'reason', 'not_retained'),
|
|
48
|
+
'details', jsonb_build_array(jsonb_build_object(
|
|
49
|
+
'path', '$',
|
|
50
|
+
'kind', 'object',
|
|
51
|
+
'originalBytes', original_bytes
|
|
52
|
+
))
|
|
53
|
+
)
|
|
54
|
+
));
|
|
55
|
+
|
|
56
|
+
-- Size fields affect their own JSON width. Converge to an exact fixed point
|
|
57
|
+
-- without retaining any omitted content; fail the write rather than publish
|
|
58
|
+
-- false accounting if PostgreSQL's representation ever stops converging.
|
|
59
|
+
FOR pass IN 1..8 LOOP
|
|
60
|
+
delivered_bytes := octet_length(bounded::text);
|
|
61
|
+
EXIT WHEN (bounded #>> '{truncation,deliveredBytes}')::integer = delivered_bytes
|
|
62
|
+
AND (bounded #>> '{truncation,omittedBytes}')::integer
|
|
63
|
+
= greatest(0, original_bytes - delivered_bytes)
|
|
64
|
+
AND (bounded #>> '{truncation,estimatedDeliveredTokens}')::integer
|
|
65
|
+
= ceil(delivered_bytes / 4.0)::integer;
|
|
66
|
+
bounded := jsonb_set(bounded, '{truncation,deliveredBytes}', to_jsonb(delivered_bytes), false);
|
|
67
|
+
bounded := jsonb_set(
|
|
68
|
+
bounded,
|
|
69
|
+
'{truncation,omittedBytes}',
|
|
70
|
+
to_jsonb(greatest(0, original_bytes - delivered_bytes)),
|
|
71
|
+
false
|
|
72
|
+
);
|
|
73
|
+
bounded := jsonb_set(
|
|
74
|
+
bounded,
|
|
75
|
+
'{truncation,estimatedDeliveredTokens}',
|
|
76
|
+
to_jsonb(ceil(delivered_bytes / 4.0)::integer),
|
|
77
|
+
false
|
|
78
|
+
);
|
|
79
|
+
END LOOP;
|
|
80
|
+
|
|
81
|
+
delivered_bytes := octet_length(bounded::text);
|
|
82
|
+
IF (bounded #>> '{truncation,deliveredBytes}')::integer <> delivered_bytes
|
|
83
|
+
OR (bounded #>> '{truncation,omittedBytes}')::integer
|
|
84
|
+
<> greatest(0, original_bytes - delivered_bytes)
|
|
85
|
+
OR (bounded #>> '{truncation,estimatedDeliveredTokens}')::integer
|
|
86
|
+
<> ceil(delivered_bytes / 4.0)::integer THEN
|
|
87
|
+
RAISE EXCEPTION 'session event payload accounting failed to converge';
|
|
88
|
+
END IF;
|
|
89
|
+
|
|
90
|
+
RETURN bounded;
|
|
91
|
+
END;
|
|
92
|
+
$function$;
|
|
93
|
+
|
|
94
|
+
CREATE OR REPLACE FUNCTION opengeni_private.bound_session_event_payload()
|
|
95
|
+
RETURNS trigger
|
|
96
|
+
LANGUAGE plpgsql
|
|
97
|
+
SET search_path = pg_catalog
|
|
98
|
+
AS $function$
|
|
99
|
+
DECLARE
|
|
100
|
+
original_type text := NEW.type;
|
|
101
|
+
original_event_bytes integer;
|
|
102
|
+
fields jsonb := '[]'::jsonb;
|
|
103
|
+
delivered text;
|
|
104
|
+
BEGIN
|
|
105
|
+
original_event_bytes := octet_length(row_to_json(NEW)::text);
|
|
106
|
+
-- Bound every free-form envelope string by UTF-8 bytes. left() uses
|
|
107
|
+
-- characters, so one quarter of each byte limit is a conservative bound for
|
|
108
|
+
-- both ASCII and four-byte Unicode without ever splitting a code point.
|
|
109
|
+
IF octet_length(NEW.type) > 256 OR position(E'\n' in NEW.type) > 0
|
|
110
|
+
OR position(E'\r' in NEW.type) > 0 THEN
|
|
111
|
+
delivered := 'session.event.envelope_omitted';
|
|
112
|
+
fields := fields || jsonb_build_array(jsonb_build_object(
|
|
113
|
+
'field', 'type',
|
|
114
|
+
'originalBytes', octet_length(NEW.type),
|
|
115
|
+
'deliveredBytes', octet_length(delivered)
|
|
116
|
+
));
|
|
117
|
+
NEW.type := delivered;
|
|
118
|
+
END IF;
|
|
119
|
+
|
|
120
|
+
IF NEW.client_event_id IS NOT NULL AND octet_length(NEW.client_event_id) > 1024 THEN
|
|
121
|
+
delivered := left(NEW.client_event_id, 256);
|
|
122
|
+
fields := fields || jsonb_build_array(jsonb_build_object(
|
|
123
|
+
'field', 'clientEventId',
|
|
124
|
+
'originalBytes', octet_length(NEW.client_event_id),
|
|
125
|
+
'deliveredBytes', octet_length(delivered)
|
|
126
|
+
));
|
|
127
|
+
NEW.client_event_id := delivered;
|
|
128
|
+
END IF;
|
|
129
|
+
|
|
130
|
+
IF NEW.producer_id IS NOT NULL AND octet_length(NEW.producer_id) > 1024 THEN
|
|
131
|
+
delivered := left(NEW.producer_id, 256);
|
|
132
|
+
fields := fields || jsonb_build_array(jsonb_build_object(
|
|
133
|
+
'field', 'producerId',
|
|
134
|
+
'originalBytes', octet_length(NEW.producer_id),
|
|
135
|
+
'deliveredBytes', octet_length(delivered)
|
|
136
|
+
));
|
|
137
|
+
NEW.producer_id := delivered;
|
|
138
|
+
END IF;
|
|
139
|
+
|
|
140
|
+
IF NEW.turn_association IS NOT NULL AND NEW.turn_association NOT IN (
|
|
141
|
+
'current', 'late_rejected', 'duplicate'
|
|
142
|
+
) THEN
|
|
143
|
+
fields := fields || jsonb_build_array(jsonb_build_object(
|
|
144
|
+
'field', 'turnAssociation',
|
|
145
|
+
'originalBytes', octet_length(NEW.turn_association),
|
|
146
|
+
'deliveredBytes', 0
|
|
147
|
+
));
|
|
148
|
+
NEW.turn_association := NULL;
|
|
149
|
+
END IF;
|
|
150
|
+
|
|
151
|
+
IF NEW.duplicate_reason IS NOT NULL AND octet_length(NEW.duplicate_reason) > 4096 THEN
|
|
152
|
+
delivered := left(NEW.duplicate_reason, 1024);
|
|
153
|
+
fields := fields || jsonb_build_array(jsonb_build_object(
|
|
154
|
+
'field', 'duplicateReason',
|
|
155
|
+
'originalBytes', octet_length(NEW.duplicate_reason),
|
|
156
|
+
'deliveredBytes', octet_length(delivered)
|
|
157
|
+
));
|
|
158
|
+
NEW.duplicate_reason := delivered;
|
|
159
|
+
END IF;
|
|
160
|
+
|
|
161
|
+
IF jsonb_array_length(fields) > 0 THEN
|
|
162
|
+
NEW.payload := jsonb_build_object(
|
|
163
|
+
'preview', '[legacy event envelope normalized by durable audit storage guard]',
|
|
164
|
+
'originalEventBytes', original_event_bytes,
|
|
165
|
+
'originalType', left(original_type, 64),
|
|
166
|
+
'envelopeProjection', jsonb_build_object(
|
|
167
|
+
'truncated', true,
|
|
168
|
+
'surface', 'database_guard',
|
|
169
|
+
'fields', fields
|
|
170
|
+
),
|
|
171
|
+
'fullEvidence', jsonb_build_object('available', false, 'reason', 'not_retained')
|
|
172
|
+
);
|
|
173
|
+
END IF;
|
|
174
|
+
|
|
175
|
+
NEW.payload := opengeni_private.project_session_event_payload(NEW.payload);
|
|
176
|
+
RETURN NEW;
|
|
177
|
+
END;
|
|
178
|
+
$function$;
|
|
179
|
+
|
|
180
|
+
DROP TRIGGER IF EXISTS session_events_bound_payload_before_insert ON session_events;
|
|
181
|
+
CREATE TRIGGER session_events_bound_payload_before_insert
|
|
182
|
+
BEFORE INSERT OR UPDATE OF payload, type, client_event_id, producer_id, turn_association, duplicate_reason
|
|
183
|
+
ON session_events
|
|
184
|
+
FOR EACH ROW
|
|
185
|
+
EXECUTE FUNCTION opengeni_private.bound_session_event_payload();
|
|
186
|
+
|
|
187
|
+
ALTER TABLE session_events
|
|
188
|
+
ADD CONSTRAINT session_events_payload_bytes_check
|
|
189
|
+
CHECK (octet_length(payload::text) <= 65536)
|
|
190
|
+
NOT VALID,
|
|
191
|
+
ADD CONSTRAINT session_events_type_bytes_check
|
|
192
|
+
CHECK (
|
|
193
|
+
octet_length(type) <= 256
|
|
194
|
+
AND position(E'\n' in type) = 0
|
|
195
|
+
AND position(E'\r' in type) = 0
|
|
196
|
+
)
|
|
197
|
+
NOT VALID,
|
|
198
|
+
ADD CONSTRAINT session_events_client_event_id_bytes_check
|
|
199
|
+
CHECK (client_event_id IS NULL OR octet_length(client_event_id) <= 1024)
|
|
200
|
+
NOT VALID,
|
|
201
|
+
ADD CONSTRAINT session_events_producer_id_bytes_check
|
|
202
|
+
CHECK (producer_id IS NULL OR octet_length(producer_id) <= 1024)
|
|
203
|
+
NOT VALID,
|
|
204
|
+
ADD CONSTRAINT session_events_turn_association_bytes_check
|
|
205
|
+
CHECK (turn_association IS NULL OR octet_length(turn_association) <= 64)
|
|
206
|
+
NOT VALID,
|
|
207
|
+
ADD CONSTRAINT session_events_duplicate_reason_bytes_check
|
|
208
|
+
CHECK (duplicate_reason IS NULL OR octet_length(duplicate_reason) <= 4096)
|
|
209
|
+
NOT VALID;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- OPE-64: workspace-control rows are compact invalidations, not evidence blobs.
|
|
3
|
+
-- Preserve exact source byte counts while bounding the two free-form fields so
|
|
4
|
+
-- old binaries and direct writers cannot create a durable replay poison row.
|
|
5
|
+
|
|
6
|
+
SET lock_timeout = '5s';
|
|
7
|
+
SET statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
ALTER TABLE workspace_control_events
|
|
10
|
+
ADD COLUMN reason_original_bytes integer,
|
|
11
|
+
ADD COLUMN actor_original_bytes integer;
|
|
12
|
+
|
|
13
|
+
CREATE OR REPLACE FUNCTION opengeni_private.truncate_workspace_control_text(
|
|
14
|
+
source_text text,
|
|
15
|
+
max_bytes integer
|
|
16
|
+
)
|
|
17
|
+
RETURNS text
|
|
18
|
+
LANGUAGE plpgsql
|
|
19
|
+
IMMUTABLE
|
|
20
|
+
STRICT
|
|
21
|
+
SET search_path = pg_catalog
|
|
22
|
+
AS $function$
|
|
23
|
+
DECLARE
|
|
24
|
+
marker constant text := '…[truncated]';
|
|
25
|
+
prefix_budget integer := greatest(0, max_bytes - octet_length(marker));
|
|
26
|
+
candidate text;
|
|
27
|
+
excess integer;
|
|
28
|
+
BEGIN
|
|
29
|
+
IF octet_length(source_text) <= max_bytes THEN
|
|
30
|
+
RETURN source_text;
|
|
31
|
+
END IF;
|
|
32
|
+
|
|
33
|
+
-- left() is character-safe. Starting at at most prefix_budget characters
|
|
34
|
+
-- caps allocation even for a multi-megabyte legacy value; each pass removes
|
|
35
|
+
-- enough characters for the worst-case four-byte UTF-8 encoding.
|
|
36
|
+
candidate := left(source_text, prefix_budget);
|
|
37
|
+
WHILE octet_length(candidate) > prefix_budget LOOP
|
|
38
|
+
excess := octet_length(candidate) - prefix_budget;
|
|
39
|
+
candidate := left(candidate, greatest(0, char_length(candidate) - ceil(excess / 4.0)::integer));
|
|
40
|
+
END LOOP;
|
|
41
|
+
RETURN candidate || marker;
|
|
42
|
+
END;
|
|
43
|
+
$function$;
|
|
44
|
+
|
|
45
|
+
CREATE OR REPLACE FUNCTION opengeni_private.bound_workspace_control_event()
|
|
46
|
+
RETURNS trigger
|
|
47
|
+
LANGUAGE plpgsql
|
|
48
|
+
SET search_path = pg_catalog
|
|
49
|
+
AS $function$
|
|
50
|
+
DECLARE
|
|
51
|
+
source_bytes integer;
|
|
52
|
+
BEGIN
|
|
53
|
+
IF NEW.reason IS NULL THEN
|
|
54
|
+
NEW.reason_original_bytes := NULL;
|
|
55
|
+
ELSE
|
|
56
|
+
source_bytes := octet_length(NEW.reason);
|
|
57
|
+
IF TG_OP = 'INSERT' THEN
|
|
58
|
+
NEW.reason_original_bytes := greatest(source_bytes, coalesce(NEW.reason_original_bytes, 0));
|
|
59
|
+
ELSIF NEW.reason IS DISTINCT FROM OLD.reason THEN
|
|
60
|
+
-- An update from an old binary carries the old metadata column even
|
|
61
|
+
-- though it knows only `reason`. A genuinely changed raw value starts a
|
|
62
|
+
-- new byte fact instead of inheriting the previous value's larger count.
|
|
63
|
+
NEW.reason_original_bytes := source_bytes;
|
|
64
|
+
ELSE
|
|
65
|
+
NEW.reason_original_bytes := greatest(
|
|
66
|
+
source_bytes,
|
|
67
|
+
coalesce(NEW.reason_original_bytes, OLD.reason_original_bytes, 0)
|
|
68
|
+
);
|
|
69
|
+
END IF;
|
|
70
|
+
NEW.reason := opengeni_private.truncate_workspace_control_text(NEW.reason, 8192);
|
|
71
|
+
END IF;
|
|
72
|
+
|
|
73
|
+
source_bytes := octet_length(NEW.actor);
|
|
74
|
+
IF TG_OP = 'INSERT' THEN
|
|
75
|
+
NEW.actor_original_bytes := greatest(source_bytes, coalesce(NEW.actor_original_bytes, 0));
|
|
76
|
+
ELSIF NEW.actor IS DISTINCT FROM OLD.actor THEN
|
|
77
|
+
NEW.actor_original_bytes := source_bytes;
|
|
78
|
+
ELSE
|
|
79
|
+
NEW.actor_original_bytes := greatest(
|
|
80
|
+
source_bytes,
|
|
81
|
+
coalesce(NEW.actor_original_bytes, OLD.actor_original_bytes, 0)
|
|
82
|
+
);
|
|
83
|
+
END IF;
|
|
84
|
+
NEW.actor := opengeni_private.truncate_workspace_control_text(NEW.actor, 1024);
|
|
85
|
+
RETURN NEW;
|
|
86
|
+
END;
|
|
87
|
+
$function$;
|
|
88
|
+
|
|
89
|
+
CREATE TRIGGER workspace_control_events_bound_fields
|
|
90
|
+
BEFORE INSERT OR UPDATE OF reason, actor, reason_original_bytes, actor_original_bytes
|
|
91
|
+
ON workspace_control_events
|
|
92
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.bound_workspace_control_event();
|
|
93
|
+
|
|
94
|
+
-- Rewrite only historical rows that violate the new durable caps. Already
|
|
95
|
+
-- bounded rows are safe to serve as-is and retain null metadata as the explicit
|
|
96
|
+
-- rolling-upgrade legacy shape; avoiding a blanket UPDATE keeps installation
|
|
97
|
+
-- work proportional to poison rows instead of table cardinality. The original
|
|
98
|
+
-- counts are supplied in the same statement so truncation never erases truth.
|
|
99
|
+
UPDATE workspace_control_events
|
|
100
|
+
SET
|
|
101
|
+
reason_original_bytes = CASE WHEN reason IS NULL THEN NULL ELSE octet_length(reason) END,
|
|
102
|
+
actor_original_bytes = octet_length(actor),
|
|
103
|
+
reason = reason,
|
|
104
|
+
actor = actor
|
|
105
|
+
WHERE octet_length(reason) > 8192
|
|
106
|
+
OR octet_length(actor) > 1024;
|
|
107
|
+
|
|
108
|
+
ALTER TABLE workspace_control_events
|
|
109
|
+
ADD CONSTRAINT workspace_control_events_reason_bytes_check
|
|
110
|
+
CHECK (reason IS NULL OR octet_length(reason) <= 8192) NOT VALID,
|
|
111
|
+
ADD CONSTRAINT workspace_control_events_actor_bytes_check
|
|
112
|
+
CHECK (octet_length(actor) <= 1024) NOT VALID,
|
|
113
|
+
ADD CONSTRAINT workspace_control_events_original_bytes_check
|
|
114
|
+
CHECK (
|
|
115
|
+
(reason IS NULL AND reason_original_bytes IS NULL)
|
|
116
|
+
OR (
|
|
117
|
+
reason IS NOT NULL
|
|
118
|
+
AND (
|
|
119
|
+
reason_original_bytes IS NULL
|
|
120
|
+
OR reason_original_bytes >= octet_length(reason)
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
) NOT VALID,
|
|
124
|
+
ADD CONSTRAINT workspace_control_events_actor_original_bytes_check
|
|
125
|
+
CHECK (
|
|
126
|
+
actor_original_bytes IS NULL
|
|
127
|
+
OR actor_original_bytes >= octet_length(actor)
|
|
128
|
+
) NOT VALID;
|
|
129
|
+
|
|
130
|
+
ALTER TABLE workspace_control_events
|
|
131
|
+
VALIDATE CONSTRAINT workspace_control_events_reason_bytes_check,
|
|
132
|
+
VALIDATE CONSTRAINT workspace_control_events_actor_bytes_check,
|
|
133
|
+
VALIDATE CONSTRAINT workspace_control_events_original_bytes_check,
|
|
134
|
+
VALIDATE CONSTRAINT workspace_control_events_actor_original_bytes_check;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- OPE-64: finish the 0067 expand/contract sequence. The trigger has bounded
|
|
3
|
+
-- every new/updated row since 0067; now rewrite only legacy violations through
|
|
4
|
+
-- that trigger and validate the durable storage invariant.
|
|
5
|
+
|
|
6
|
+
SET lock_timeout = '5s';
|
|
7
|
+
SET statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
UPDATE session_events
|
|
10
|
+
SET
|
|
11
|
+
payload = payload,
|
|
12
|
+
type = type,
|
|
13
|
+
client_event_id = client_event_id,
|
|
14
|
+
producer_id = producer_id,
|
|
15
|
+
turn_association = turn_association,
|
|
16
|
+
duplicate_reason = duplicate_reason
|
|
17
|
+
WHERE octet_length(payload::text) > 65536
|
|
18
|
+
OR octet_length(type) > 256
|
|
19
|
+
OR position(E'\n' in type) > 0
|
|
20
|
+
OR position(E'\r' in type) > 0
|
|
21
|
+
OR (client_event_id IS NOT NULL AND octet_length(client_event_id) > 1024)
|
|
22
|
+
OR (producer_id IS NOT NULL AND octet_length(producer_id) > 1024)
|
|
23
|
+
OR (turn_association IS NOT NULL AND octet_length(turn_association) > 64)
|
|
24
|
+
OR (duplicate_reason IS NOT NULL AND octet_length(duplicate_reason) > 4096);
|
|
25
|
+
|
|
26
|
+
ALTER TABLE session_events
|
|
27
|
+
VALIDATE CONSTRAINT session_events_payload_bytes_check,
|
|
28
|
+
VALIDATE CONSTRAINT session_events_type_bytes_check,
|
|
29
|
+
VALIDATE CONSTRAINT session_events_client_event_id_bytes_check,
|
|
30
|
+
VALIDATE CONSTRAINT session_events_producer_id_bytes_check,
|
|
31
|
+
VALIDATE CONSTRAINT session_events_turn_association_bytes_check,
|
|
32
|
+
VALIDATE CONSTRAINT session_events_duplicate_reason_bytes_check;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- opengeni:concurrent-index lock-timeout=5s
|
|
3
|
+
CREATE INDEX CONCURRENTLY "session_events_workspace_session_monitoring_tail_idx"
|
|
4
|
+
ON "session_events" ("workspace_id", "session_id", "sequence" DESC)
|
|
5
|
+
WHERE "type" NOT IN (
|
|
6
|
+
'agent.message.delta',
|
|
7
|
+
'agent.reasoning.delta',
|
|
8
|
+
'sandbox.command.output.delta',
|
|
9
|
+
'terminal.pty.output.delta'
|
|
10
|
+
);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- OPE-65: transactional workspace revisions make updated-order discovery and
|
|
3
|
+
-- its next incremental scan one gap-free handoff independent of application
|
|
4
|
+
-- clocks. Existing sessions remain revision zero until their next activity.
|
|
5
|
+
|
|
6
|
+
SET lock_timeout = '5s';
|
|
7
|
+
SET statement_timeout = '10min';
|
|
8
|
+
|
|
9
|
+
CREATE TABLE "workspace_session_activity_revisions" (
|
|
10
|
+
"workspace_id" uuid PRIMARY KEY,
|
|
11
|
+
"account_id" uuid NOT NULL,
|
|
12
|
+
"revision" bigint NOT NULL DEFAULT 0,
|
|
13
|
+
CONSTRAINT "workspace_session_activity_revisions_workspace_account_fk"
|
|
14
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
15
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
16
|
+
CONSTRAINT "workspace_session_activity_revisions_revision_check"
|
|
17
|
+
CHECK ("revision" >= 0)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
ALTER TABLE "workspace_session_activity_revisions" ENABLE ROW LEVEL SECURITY;
|
|
21
|
+
ALTER TABLE "workspace_session_activity_revisions" FORCE ROW LEVEL SECURITY;
|
|
22
|
+
CREATE POLICY workspace_isolation ON "workspace_session_activity_revisions"
|
|
23
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
24
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
25
|
+
|
|
26
|
+
-- PostgreSQL installs this constant default without rewriting existing rows.
|
|
27
|
+
-- Revision zero is an intentional legacy bucket ordered by (updated_at, id).
|
|
28
|
+
ALTER TABLE "sessions"
|
|
29
|
+
ADD COLUMN "activity_revision" bigint NOT NULL DEFAULT 0;
|
|
30
|
+
|
|
31
|
+
CREATE OR REPLACE FUNCTION opengeni_private.assign_session_activity_revision()
|
|
32
|
+
RETURNS trigger
|
|
33
|
+
LANGUAGE plpgsql
|
|
34
|
+
SET search_path = pg_catalog
|
|
35
|
+
AS $function$
|
|
36
|
+
DECLARE
|
|
37
|
+
next_revision bigint;
|
|
38
|
+
BEGIN
|
|
39
|
+
EXECUTE format(
|
|
40
|
+
'INSERT INTO %I.workspace_session_activity_revisions AS counter '
|
|
41
|
+
|| '(workspace_id, account_id, revision) VALUES ($1, $2, 1) '
|
|
42
|
+
|| 'ON CONFLICT (workspace_id) DO UPDATE SET '
|
|
43
|
+
|| 'account_id = excluded.account_id, revision = counter.revision + 1 '
|
|
44
|
+
|| 'RETURNING revision',
|
|
45
|
+
TG_TABLE_SCHEMA
|
|
46
|
+
)
|
|
47
|
+
INTO next_revision
|
|
48
|
+
USING NEW.workspace_id, NEW.account_id;
|
|
49
|
+
|
|
50
|
+
NEW.activity_revision := next_revision;
|
|
51
|
+
RETURN NEW;
|
|
52
|
+
END;
|
|
53
|
+
$function$;
|
|
54
|
+
|
|
55
|
+
CREATE TRIGGER sessions_assign_activity_revision
|
|
56
|
+
BEFORE INSERT OR UPDATE OF updated_at, activity_revision
|
|
57
|
+
ON "sessions"
|
|
58
|
+
FOR EACH ROW EXECUTE FUNCTION opengeni_private.assign_session_activity_revision();
|
|
59
|
+
|
|
60
|
+
DO $grants$
|
|
61
|
+
DECLARE target_schema text := current_schema();
|
|
62
|
+
BEGIN
|
|
63
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
64
|
+
EXECUTE format(
|
|
65
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.workspace_session_activity_revisions TO opengeni_app',
|
|
66
|
+
target_schema
|
|
67
|
+
);
|
|
68
|
+
END IF;
|
|
69
|
+
END $grants$;
|
|
70
|
+
|
|
71
|
+
RESET statement_timeout;
|
|
72
|
+
RESET lock_timeout;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Migration 0063 recreates this SECURITY DEFINER function and revokes its
|
|
3
|
+
-- default PUBLIC authority. Restore only the runtime dispatcher's exact grant;
|
|
4
|
+
-- production Helm runs migrations without a separate provision-roles pass.
|
|
5
|
+
REVOKE ALL ON FUNCTION opengeni_private.claim_session_workflow_wakes(integer) FROM PUBLIC;
|
|
6
|
+
|
|
7
|
+
DO $$
|
|
8
|
+
BEGIN
|
|
9
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
10
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.claim_session_workflow_wakes(integer)
|
|
11
|
+
TO opengeni_app;
|
|
12
|
+
END IF;
|
|
13
|
+
END $$;
|