@opengeni/db 0.13.1 → 0.14.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-N4WTE6SB.js → chunk-PQKVUWQW.js} +20 -6
- package/dist/chunk-PQKVUWQW.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +817 -288
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +94 -13
- package/dist/{schema-D-hyrsFt.d.ts → schema-FoPub9yt.d.ts} +70 -15
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +1 -1
- package/drizzle/0132_connection_subject_isolation.sql +100 -0
- package/drizzle/0133_session_skills.sql +3 -0
- package/drizzle/0134_session_first_party_mcp_tools.sql +15 -0
- package/drizzle/0135_durable_machine_input_batches.sql +359 -0
- package/package.json +3 -3
- package/src/connection-token-resolver.ts +17 -7
- package/src/index.ts +1095 -344
- package/src/schema.ts +26 -6
- package/src/session-queue-commands.ts +22 -14
- package/dist/chunk-N4WTE6SB.js.map +0 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Make personal integration credentials subject-owned at the database boundary.
|
|
3
|
+
-- Every old API/control/turn writer MUST be stopped before activation: old writers
|
|
4
|
+
-- do not establish opengeni.subject_id and can persist workspace-visible OAuth refs.
|
|
5
|
+
|
|
6
|
+
SET LOCAL lock_timeout = '5s';
|
|
7
|
+
SET LOCAL statement_timeout = '30min';
|
|
8
|
+
|
|
9
|
+
DO $maintenance_preflight_guard$
|
|
10
|
+
BEGIN
|
|
11
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
|
|
12
|
+
AND EXISTS (
|
|
13
|
+
SELECT 1
|
|
14
|
+
FROM pg_stat_activity
|
|
15
|
+
WHERE datname = current_database()
|
|
16
|
+
AND usename = 'opengeni_app'
|
|
17
|
+
AND pid <> pg_backend_pid()
|
|
18
|
+
)
|
|
19
|
+
THEN
|
|
20
|
+
RAISE EXCEPTION
|
|
21
|
+
'connection subject-isolation activation requires all opengeni_app sessions to be stopped'
|
|
22
|
+
USING ERRCODE = '55000';
|
|
23
|
+
END IF;
|
|
24
|
+
END
|
|
25
|
+
$maintenance_preflight_guard$;
|
|
26
|
+
|
|
27
|
+
LOCK TABLE connections IN ACCESS EXCLUSIVE MODE;
|
|
28
|
+
LOCK TABLE capability_installations IN ACCESS EXCLUSIVE MODE;
|
|
29
|
+
|
|
30
|
+
DO $maintenance_guard$
|
|
31
|
+
BEGIN
|
|
32
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
|
|
33
|
+
AND EXISTS (
|
|
34
|
+
SELECT 1
|
|
35
|
+
FROM pg_stat_activity
|
|
36
|
+
WHERE datname = current_database()
|
|
37
|
+
AND usename = 'opengeni_app'
|
|
38
|
+
AND pid <> pg_backend_pid()
|
|
39
|
+
)
|
|
40
|
+
THEN
|
|
41
|
+
RAISE EXCEPTION
|
|
42
|
+
'connection subject-isolation activation requires all opengeni_app sessions to be stopped'
|
|
43
|
+
USING ERRCODE = '55000';
|
|
44
|
+
END IF;
|
|
45
|
+
END
|
|
46
|
+
$maintenance_guard$;
|
|
47
|
+
|
|
48
|
+
-- Migration 0039 initially stored generic MCP OAuth rows as workspace-shared.
|
|
49
|
+
-- Backfill only Slack's exact official hosted MCP endpoint, and only where the
|
|
50
|
+
-- OAuth callback recorded the authenticating subject. Workspace app installs,
|
|
51
|
+
-- manual Slack credentials, and ambiguous legacy rows are deliberately untouched.
|
|
52
|
+
UPDATE connections
|
|
53
|
+
SET subject_id = created_by_subject_id,
|
|
54
|
+
updated_at = now()
|
|
55
|
+
WHERE subject_id IS NULL
|
|
56
|
+
AND kind = 'oauth2'
|
|
57
|
+
AND provider_domain = 'slack.com'
|
|
58
|
+
AND metadata ->> 'mcpUrl' = 'https://mcp.slack.com/mcp'
|
|
59
|
+
AND created_by_subject_id IS NOT NULL
|
|
60
|
+
AND btrim(created_by_subject_id) <> '';
|
|
61
|
+
|
|
62
|
+
-- Capability installation metadata is workspace-visible. Preserve only a
|
|
63
|
+
-- generic subject selector; the concrete personal connection UUID remains in
|
|
64
|
+
-- the subject-protected connection row and is selected at turn execution.
|
|
65
|
+
UPDATE capability_installations AS installation
|
|
66
|
+
SET config = jsonb_set(
|
|
67
|
+
installation.config #- '{connectionRef,connectionId}',
|
|
68
|
+
'{connectionRef,subjectScope}',
|
|
69
|
+
'"subject"'::jsonb,
|
|
70
|
+
true
|
|
71
|
+
)
|
|
72
|
+
WHERE jsonb_typeof(installation.config -> 'connectionRef') = 'object'
|
|
73
|
+
AND EXISTS (
|
|
74
|
+
SELECT 1
|
|
75
|
+
FROM connections AS connection
|
|
76
|
+
WHERE connection.id::text = installation.config #>> '{connectionRef,connectionId}'
|
|
77
|
+
AND connection.account_id = installation.account_id
|
|
78
|
+
AND connection.workspace_id = installation.workspace_id
|
|
79
|
+
AND connection.subject_id IS NOT NULL
|
|
80
|
+
AND connection.kind = 'oauth2'
|
|
81
|
+
AND connection.provider_domain = 'slack.com'
|
|
82
|
+
AND connection.metadata ->> 'mcpUrl' = 'https://mcp.slack.com/mcp'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
DROP POLICY IF EXISTS workspace_isolation ON connections;
|
|
86
|
+
CREATE POLICY workspace_isolation ON connections
|
|
87
|
+
USING (
|
|
88
|
+
opengeni_private.workspace_rls_visible(account_id, workspace_id)
|
|
89
|
+
AND (
|
|
90
|
+
subject_id IS NULL
|
|
91
|
+
OR subject_id = nullif(current_setting('opengeni.subject_id', true), '')
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
WITH CHECK (
|
|
95
|
+
opengeni_private.workspace_rls_visible(account_id, workspace_id)
|
|
96
|
+
AND (
|
|
97
|
+
subject_id IS NULL
|
|
98
|
+
OR subject_id = nullif(current_setting('opengeni.subject_id', true), '')
|
|
99
|
+
)
|
|
100
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Per-session model-visible selection for the broad first-party MCP server.
|
|
3
|
+
-- NULL resolves to the fixed minimal default; [] intentionally selects none.
|
|
4
|
+
ALTER TABLE "sessions"
|
|
5
|
+
ADD COLUMN "first_party_mcp_tools" jsonb;
|
|
6
|
+
|
|
7
|
+
ALTER TABLE "sessions"
|
|
8
|
+
ADD CONSTRAINT "sessions_first_party_mcp_tools_array_chk"
|
|
9
|
+
CHECK (
|
|
10
|
+
"first_party_mcp_tools" IS NULL
|
|
11
|
+
OR jsonb_typeof("first_party_mcp_tools") = 'array'
|
|
12
|
+
) NOT VALID;
|
|
13
|
+
|
|
14
|
+
ALTER TABLE "sessions"
|
|
15
|
+
VALIDATE CONSTRAINT "sessions_first_party_mcp_tools_array_chk";
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Machine inputs become append-only model memory. This one-way cutover also
|
|
3
|
+
-- reconstructs every previously delivered batch and removes the old deferred
|
|
4
|
+
-- retry state; old workers must be drained before this migration runs.
|
|
5
|
+
|
|
6
|
+
ALTER TABLE "session_system_updates"
|
|
7
|
+
ADD COLUMN "delivered_history_item_id" uuid;
|
|
8
|
+
|
|
9
|
+
UPDATE "session_system_updates"
|
|
10
|
+
SET "state" = 'pending'
|
|
11
|
+
WHERE "state" = 'deferred';
|
|
12
|
+
|
|
13
|
+
WITH ranked_pending_steers AS (
|
|
14
|
+
SELECT
|
|
15
|
+
id,
|
|
16
|
+
row_number() OVER (
|
|
17
|
+
PARTITION BY workspace_id, session_id
|
|
18
|
+
ORDER BY created_at DESC, id DESC
|
|
19
|
+
) AS rank
|
|
20
|
+
FROM "session_system_updates"
|
|
21
|
+
WHERE kind = 'agent_steer_instruction'
|
|
22
|
+
AND state = 'pending'
|
|
23
|
+
)
|
|
24
|
+
UPDATE "session_system_updates" updates
|
|
25
|
+
SET state = 'superseded'
|
|
26
|
+
FROM ranked_pending_steers ranked
|
|
27
|
+
WHERE updates.id = ranked.id
|
|
28
|
+
AND ranked.rank > 1;
|
|
29
|
+
|
|
30
|
+
ALTER TABLE "session_system_updates"
|
|
31
|
+
DROP CONSTRAINT "system_updates_state_check";
|
|
32
|
+
|
|
33
|
+
ALTER TABLE "session_system_updates"
|
|
34
|
+
ADD CONSTRAINT "system_updates_state_check"
|
|
35
|
+
CHECK ("state" IN ('pending', 'delivered', 'cancelled', 'superseded', 'failed'));
|
|
36
|
+
|
|
37
|
+
WITH delivered_batches AS (
|
|
38
|
+
SELECT
|
|
39
|
+
updates.account_id,
|
|
40
|
+
updates.workspace_id,
|
|
41
|
+
updates.session_id,
|
|
42
|
+
updates.delivered_turn_id AS turn_id,
|
|
43
|
+
gen_random_uuid() AS history_item_id,
|
|
44
|
+
min(updates.delivered_at) AS delivered_at,
|
|
45
|
+
jsonb_build_object(
|
|
46
|
+
'type', 'message',
|
|
47
|
+
'role', 'system',
|
|
48
|
+
'content',
|
|
49
|
+
'[OpenGeni internal updates]' || E'\n' ||
|
|
50
|
+
'These platform updates were delivered together for this inference. They are not human prompts.' || E'\n' ||
|
|
51
|
+
jsonb_build_object(
|
|
52
|
+
'updates',
|
|
53
|
+
jsonb_agg(
|
|
54
|
+
jsonb_build_object(
|
|
55
|
+
'id', updates.id,
|
|
56
|
+
'kind', updates.kind,
|
|
57
|
+
'classification', updates.classification,
|
|
58
|
+
'sourceId', updates.source_id,
|
|
59
|
+
'summary', updates.summary,
|
|
60
|
+
'payload', updates.payload,
|
|
61
|
+
'lineage', updates.lineage
|
|
62
|
+
)
|
|
63
|
+
ORDER BY
|
|
64
|
+
CASE WHEN updates.kind = 'agent_steer_instruction' THEN 1 ELSE 0 END,
|
|
65
|
+
updates.created_at,
|
|
66
|
+
updates.id
|
|
67
|
+
)
|
|
68
|
+
)::text
|
|
69
|
+
) AS item
|
|
70
|
+
FROM "session_system_updates" updates
|
|
71
|
+
WHERE updates.state = 'delivered'
|
|
72
|
+
AND updates.delivered_turn_id IS NOT NULL
|
|
73
|
+
AND updates.delivered_history_item_id IS NULL
|
|
74
|
+
GROUP BY
|
|
75
|
+
updates.account_id,
|
|
76
|
+
updates.workspace_id,
|
|
77
|
+
updates.session_id,
|
|
78
|
+
updates.delivered_turn_id
|
|
79
|
+
),
|
|
80
|
+
anchored AS (
|
|
81
|
+
SELECT
|
|
82
|
+
batches.*,
|
|
83
|
+
turns.position AS turn_position,
|
|
84
|
+
turns.created_at AS turn_created_at,
|
|
85
|
+
turns.id AS turn_order_id,
|
|
86
|
+
NOT EXISTS (
|
|
87
|
+
SELECT 1
|
|
88
|
+
FROM "session_events" reset_event
|
|
89
|
+
WHERE reset_event.workspace_id = batches.workspace_id
|
|
90
|
+
AND reset_event.session_id = batches.session_id
|
|
91
|
+
AND reset_event.type IN ('session.context.compacted', 'session.context.cleared')
|
|
92
|
+
AND reset_event.occurred_at >= coalesce(batches.delivered_at, turns.created_at)
|
|
93
|
+
) AS preserve_active,
|
|
94
|
+
(
|
|
95
|
+
SELECT max(history.position)
|
|
96
|
+
FROM "session_history_items" history
|
|
97
|
+
WHERE history.workspace_id = batches.workspace_id
|
|
98
|
+
AND history.session_id = batches.session_id
|
|
99
|
+
AND history.turn_id = batches.turn_id
|
|
100
|
+
AND history.active
|
|
101
|
+
AND history.item ->> 'type' = 'message'
|
|
102
|
+
AND history.item ->> 'role' = 'user'
|
|
103
|
+
) AS user_position,
|
|
104
|
+
(
|
|
105
|
+
SELECT min(history.position)
|
|
106
|
+
FROM "session_history_items" history
|
|
107
|
+
WHERE history.workspace_id = batches.workspace_id
|
|
108
|
+
AND history.session_id = batches.session_id
|
|
109
|
+
AND history.turn_id = batches.turn_id
|
|
110
|
+
AND history.active
|
|
111
|
+
) AS turn_first_position,
|
|
112
|
+
(
|
|
113
|
+
SELECT coalesce(max(history.position), -1)
|
|
114
|
+
FROM "session_history_items" history
|
|
115
|
+
WHERE history.workspace_id = batches.workspace_id
|
|
116
|
+
AND history.session_id = batches.session_id
|
|
117
|
+
AND history.active
|
|
118
|
+
) AS active_tail_position,
|
|
119
|
+
(
|
|
120
|
+
SELECT coalesce(max(history.position), -1)
|
|
121
|
+
FROM "session_history_items" history
|
|
122
|
+
WHERE history.workspace_id = batches.workspace_id
|
|
123
|
+
AND history.session_id = batches.session_id
|
|
124
|
+
) AS audit_tail_position,
|
|
125
|
+
(
|
|
126
|
+
SELECT max(history.position)
|
|
127
|
+
FROM "session_history_items" history
|
|
128
|
+
JOIN "session_turns" history_turn
|
|
129
|
+
ON history_turn.workspace_id = history.workspace_id
|
|
130
|
+
AND history_turn.id = history.turn_id
|
|
131
|
+
WHERE history.workspace_id = batches.workspace_id
|
|
132
|
+
AND history.session_id = batches.session_id
|
|
133
|
+
AND history.active
|
|
134
|
+
AND (history_turn.position, history_turn.created_at, history_turn.id)
|
|
135
|
+
< (turns.position, turns.created_at, turns.id)
|
|
136
|
+
) AS previous_active_position,
|
|
137
|
+
(
|
|
138
|
+
SELECT min(history.position)
|
|
139
|
+
FROM "session_history_items" history
|
|
140
|
+
JOIN "session_turns" history_turn
|
|
141
|
+
ON history_turn.workspace_id = history.workspace_id
|
|
142
|
+
AND history_turn.id = history.turn_id
|
|
143
|
+
WHERE history.workspace_id = batches.workspace_id
|
|
144
|
+
AND history.session_id = batches.session_id
|
|
145
|
+
AND history.active
|
|
146
|
+
AND (history_turn.position, history_turn.created_at, history_turn.id)
|
|
147
|
+
> (turns.position, turns.created_at, turns.id)
|
|
148
|
+
) AS next_active_position,
|
|
149
|
+
row_number() OVER (
|
|
150
|
+
PARTITION BY batches.workspace_id, batches.session_id
|
|
151
|
+
ORDER BY turns.position, turns.created_at, turns.id
|
|
152
|
+
) AS turn_ordinal
|
|
153
|
+
FROM delivered_batches batches
|
|
154
|
+
JOIN "session_turns" turns
|
|
155
|
+
ON turns.workspace_id = batches.workspace_id
|
|
156
|
+
AND turns.id = batches.turn_id
|
|
157
|
+
),
|
|
158
|
+
gap_ranked AS (
|
|
159
|
+
SELECT
|
|
160
|
+
anchored.*,
|
|
161
|
+
row_number() OVER (
|
|
162
|
+
PARTITION BY
|
|
163
|
+
workspace_id,
|
|
164
|
+
session_id,
|
|
165
|
+
preserve_active,
|
|
166
|
+
user_position,
|
|
167
|
+
turn_first_position,
|
|
168
|
+
previous_active_position,
|
|
169
|
+
next_active_position
|
|
170
|
+
ORDER BY turn_position, turn_created_at, turn_order_id
|
|
171
|
+
) AS gap_ordinal,
|
|
172
|
+
count(*) OVER (
|
|
173
|
+
PARTITION BY
|
|
174
|
+
workspace_id,
|
|
175
|
+
session_id,
|
|
176
|
+
preserve_active,
|
|
177
|
+
user_position,
|
|
178
|
+
turn_first_position,
|
|
179
|
+
previous_active_position,
|
|
180
|
+
next_active_position
|
|
181
|
+
) AS gap_count
|
|
182
|
+
FROM anchored
|
|
183
|
+
),
|
|
184
|
+
positioned AS (
|
|
185
|
+
SELECT
|
|
186
|
+
gap_ranked.*,
|
|
187
|
+
CASE
|
|
188
|
+
WHEN NOT preserve_active THEN audit_tail_position + turn_ordinal
|
|
189
|
+
WHEN user_position IS NOT NULL THEN
|
|
190
|
+
user_position
|
|
191
|
+
+ (
|
|
192
|
+
coalesce(
|
|
193
|
+
(
|
|
194
|
+
SELECT min(history.position)
|
|
195
|
+
FROM "session_history_items" history
|
|
196
|
+
WHERE history.workspace_id = gap_ranked.workspace_id
|
|
197
|
+
AND history.session_id = gap_ranked.session_id
|
|
198
|
+
AND history.position > user_position
|
|
199
|
+
),
|
|
200
|
+
user_position + 1
|
|
201
|
+
)
|
|
202
|
+
- user_position
|
|
203
|
+
) * gap_ordinal / (gap_count + 1)
|
|
204
|
+
WHEN turn_first_position IS NOT NULL THEN
|
|
205
|
+
coalesce(
|
|
206
|
+
(
|
|
207
|
+
SELECT max(history.position)
|
|
208
|
+
FROM "session_history_items" history
|
|
209
|
+
WHERE history.workspace_id = gap_ranked.workspace_id
|
|
210
|
+
AND history.session_id = gap_ranked.session_id
|
|
211
|
+
AND history.position < turn_first_position
|
|
212
|
+
),
|
|
213
|
+
turn_first_position - 2
|
|
214
|
+
)
|
|
215
|
+
+ (
|
|
216
|
+
turn_first_position
|
|
217
|
+
- coalesce(
|
|
218
|
+
(
|
|
219
|
+
SELECT max(history.position)
|
|
220
|
+
FROM "session_history_items" history
|
|
221
|
+
WHERE history.workspace_id = gap_ranked.workspace_id
|
|
222
|
+
AND history.session_id = gap_ranked.session_id
|
|
223
|
+
AND history.position < turn_first_position
|
|
224
|
+
),
|
|
225
|
+
turn_first_position - 2
|
|
226
|
+
)
|
|
227
|
+
) * gap_ordinal / (gap_count + 1)
|
|
228
|
+
WHEN previous_active_position IS NOT NULL AND next_active_position IS NOT NULL THEN
|
|
229
|
+
previous_active_position
|
|
230
|
+
+ (next_active_position - previous_active_position) * gap_ordinal / (gap_count + 1)
|
|
231
|
+
WHEN previous_active_position IS NOT NULL THEN
|
|
232
|
+
previous_active_position + gap_ordinal
|
|
233
|
+
WHEN next_active_position IS NOT NULL THEN
|
|
234
|
+
next_active_position - (gap_count - gap_ordinal + 1)
|
|
235
|
+
ELSE active_tail_position + gap_ordinal
|
|
236
|
+
END AS history_position
|
|
237
|
+
FROM gap_ranked
|
|
238
|
+
),
|
|
239
|
+
existing_gaps AS (
|
|
240
|
+
SELECT
|
|
241
|
+
positioned.*,
|
|
242
|
+
coalesce(
|
|
243
|
+
(
|
|
244
|
+
SELECT max(history.position)
|
|
245
|
+
FROM "session_history_items" history
|
|
246
|
+
WHERE history.workspace_id = positioned.workspace_id
|
|
247
|
+
AND history.session_id = positioned.session_id
|
|
248
|
+
AND history.position < positioned.history_position
|
|
249
|
+
),
|
|
250
|
+
positioned.history_position - 1
|
|
251
|
+
) AS existing_gap_left,
|
|
252
|
+
coalesce(
|
|
253
|
+
(
|
|
254
|
+
SELECT min(history.position)
|
|
255
|
+
FROM "session_history_items" history
|
|
256
|
+
WHERE history.workspace_id = positioned.workspace_id
|
|
257
|
+
AND history.session_id = positioned.session_id
|
|
258
|
+
AND history.position > positioned.history_position
|
|
259
|
+
),
|
|
260
|
+
positioned.history_position + 1
|
|
261
|
+
) AS existing_gap_right
|
|
262
|
+
FROM positioned
|
|
263
|
+
),
|
|
264
|
+
final_positioned AS (
|
|
265
|
+
SELECT
|
|
266
|
+
existing_gaps.*,
|
|
267
|
+
existing_gap_left
|
|
268
|
+
+ (existing_gap_right - existing_gap_left)
|
|
269
|
+
* row_number() OVER (
|
|
270
|
+
PARTITION BY
|
|
271
|
+
workspace_id,
|
|
272
|
+
session_id,
|
|
273
|
+
existing_gap_left,
|
|
274
|
+
existing_gap_right
|
|
275
|
+
ORDER BY history_position, turn_position, turn_created_at, turn_order_id
|
|
276
|
+
)
|
|
277
|
+
/ (
|
|
278
|
+
count(*) OVER (
|
|
279
|
+
PARTITION BY
|
|
280
|
+
workspace_id,
|
|
281
|
+
session_id,
|
|
282
|
+
existing_gap_left,
|
|
283
|
+
existing_gap_right
|
|
284
|
+
) + 1
|
|
285
|
+
) AS final_history_position
|
|
286
|
+
FROM existing_gaps
|
|
287
|
+
),
|
|
288
|
+
inserted AS (
|
|
289
|
+
INSERT INTO "session_history_items" (
|
|
290
|
+
"id",
|
|
291
|
+
"account_id",
|
|
292
|
+
"workspace_id",
|
|
293
|
+
"session_id",
|
|
294
|
+
"turn_id",
|
|
295
|
+
"position",
|
|
296
|
+
"item",
|
|
297
|
+
"active",
|
|
298
|
+
"producer_codex_credential_id",
|
|
299
|
+
"created_at"
|
|
300
|
+
)
|
|
301
|
+
SELECT
|
|
302
|
+
history_item_id,
|
|
303
|
+
account_id,
|
|
304
|
+
workspace_id,
|
|
305
|
+
session_id,
|
|
306
|
+
turn_id,
|
|
307
|
+
final_history_position,
|
|
308
|
+
item,
|
|
309
|
+
preserve_active,
|
|
310
|
+
NULL,
|
|
311
|
+
coalesce(delivered_at, now())
|
|
312
|
+
FROM final_positioned
|
|
313
|
+
RETURNING id, workspace_id, session_id, turn_id
|
|
314
|
+
)
|
|
315
|
+
UPDATE "session_system_updates" updates
|
|
316
|
+
SET "delivered_history_item_id" = inserted.id
|
|
317
|
+
FROM inserted
|
|
318
|
+
WHERE updates.workspace_id = inserted.workspace_id
|
|
319
|
+
AND updates.session_id = inserted.session_id
|
|
320
|
+
AND updates.delivered_turn_id = inserted.turn_id
|
|
321
|
+
AND updates.state = 'delivered'
|
|
322
|
+
AND updates.delivered_history_item_id IS NULL;
|
|
323
|
+
|
|
324
|
+
DO $$
|
|
325
|
+
BEGIN
|
|
326
|
+
IF EXISTS (
|
|
327
|
+
SELECT 1
|
|
328
|
+
FROM "session_system_updates"
|
|
329
|
+
WHERE state = 'delivered'
|
|
330
|
+
AND delivered_history_item_id IS NULL
|
|
331
|
+
) THEN
|
|
332
|
+
RAISE EXCEPTION
|
|
333
|
+
'cannot prove model-memory position for one or more delivered machine inputs';
|
|
334
|
+
END IF;
|
|
335
|
+
END
|
|
336
|
+
$$;
|
|
337
|
+
|
|
338
|
+
ALTER TABLE "session_system_updates"
|
|
339
|
+
ADD CONSTRAINT "session_system_updates_history_item_fk"
|
|
340
|
+
FOREIGN KEY ("delivered_history_item_id")
|
|
341
|
+
REFERENCES "session_history_items"("id")
|
|
342
|
+
ON DELETE RESTRICT
|
|
343
|
+
DEFERRABLE INITIALLY DEFERRED;
|
|
344
|
+
|
|
345
|
+
ALTER TABLE "session_system_updates"
|
|
346
|
+
ADD CONSTRAINT "session_system_updates_delivery_history_check"
|
|
347
|
+
CHECK (
|
|
348
|
+
("state" = 'delivered' AND "delivered_history_item_id" IS NOT NULL)
|
|
349
|
+
OR
|
|
350
|
+
("state" <> 'delivered' AND "delivered_history_item_id" IS NULL)
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
CREATE INDEX "session_system_updates_history_item_idx"
|
|
354
|
+
ON "session_system_updates" ("workspace_id", "delivered_history_item_id")
|
|
355
|
+
WHERE "delivered_history_item_id" IS NOT NULL;
|
|
356
|
+
|
|
357
|
+
CREATE UNIQUE INDEX "session_system_updates_one_pending_steer_idx"
|
|
358
|
+
ON "session_system_updates" ("workspace_id", "session_id")
|
|
359
|
+
WHERE "kind" = 'agent_steer_instruction' AND "state" = 'pending';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@opengeni/codex": "^0.2.7",
|
|
54
|
-
"@opengeni/config": "^0.7.
|
|
55
|
-
"@opengeni/contracts": "^0.
|
|
54
|
+
"@opengeni/config": "^0.7.11",
|
|
55
|
+
"@opengeni/contracts": "^0.22.0",
|
|
56
56
|
"@opengeni/network": "^0.1.1",
|
|
57
57
|
"drizzle-orm": "^0.45.2",
|
|
58
58
|
"postgres": "^3.4.7"
|
|
@@ -369,11 +369,15 @@ export function buildConnectionTokenResolver(
|
|
|
369
369
|
const load = async (
|
|
370
370
|
input: CredentialLookupInput,
|
|
371
371
|
): Promise<ConnectionCredentialForBroker | null> => {
|
|
372
|
+
const subjectOwned = input.connectionRef.subjectScope === "subject";
|
|
373
|
+
if (subjectOwned && !input.subjectId) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
372
376
|
const request: Parameters<typeof loadConnectionCredentialForBroker>[2] = {
|
|
373
377
|
workspaceId: input.workspaceId,
|
|
374
378
|
providerDomain: input.connectionRef.providerDomain,
|
|
375
|
-
|
|
376
|
-
|
|
379
|
+
allowSubjectOwned: subjectOwned,
|
|
380
|
+
...(subjectOwned ? { subjectId: input.subjectId! } : {}),
|
|
377
381
|
};
|
|
378
382
|
if (input.connectionRef.connectionId !== undefined) {
|
|
379
383
|
request.connectionId = input.connectionRef.connectionId;
|
|
@@ -381,10 +385,12 @@ export function buildConnectionTokenResolver(
|
|
|
381
385
|
if (input.connectionRef.kind !== undefined) {
|
|
382
386
|
request.kind = input.connectionRef.kind;
|
|
383
387
|
}
|
|
384
|
-
|
|
385
|
-
|
|
388
|
+
const credential = await deps.loadCredential(db, settings, request);
|
|
389
|
+
if (!credential) return null;
|
|
390
|
+
if (subjectOwned) {
|
|
391
|
+
return credential.subjectId === input.subjectId ? credential : null;
|
|
386
392
|
}
|
|
387
|
-
return
|
|
393
|
+
return credential.subjectId === null ? credential : null;
|
|
388
394
|
};
|
|
389
395
|
|
|
390
396
|
const snapshot = async (
|
|
@@ -428,7 +434,7 @@ export function buildConnectionTokenResolver(
|
|
|
428
434
|
: {}),
|
|
429
435
|
};
|
|
430
436
|
}
|
|
431
|
-
await deps.recordUsed(db, cred.workspaceId, cred.id);
|
|
437
|
+
await deps.recordUsed(db, cred.workspaceId, cred.id, cred.subjectId);
|
|
432
438
|
return {
|
|
433
439
|
status: "ok",
|
|
434
440
|
headers,
|
|
@@ -453,6 +459,7 @@ export function buildConnectionTokenResolver(
|
|
|
453
459
|
credentialEncrypted: deps.encrypt(key, JSON.stringify(refreshed.credential)),
|
|
454
460
|
expiresAt: refreshed.expiresAt,
|
|
455
461
|
lastRefreshAt: deps.now(),
|
|
462
|
+
subjectId: cred.subjectId,
|
|
456
463
|
};
|
|
457
464
|
if (refreshed.grantedScopes !== undefined) {
|
|
458
465
|
refreshRecord.grantedScopes = refreshed.grantedScopes;
|
|
@@ -462,6 +469,7 @@ export function buildConnectionTokenResolver(
|
|
|
462
469
|
const current = await load({
|
|
463
470
|
workspaceId: cred.workspaceId,
|
|
464
471
|
connectionRef: { ...ref, connectionId: cred.id },
|
|
472
|
+
...(cred.subjectId ? { subjectId: cred.subjectId } : {}),
|
|
465
473
|
});
|
|
466
474
|
if (current) {
|
|
467
475
|
return current;
|
|
@@ -470,6 +478,7 @@ export function buildConnectionTokenResolver(
|
|
|
470
478
|
const winner = await load({
|
|
471
479
|
workspaceId: cred.workspaceId,
|
|
472
480
|
connectionRef: { ...ref, connectionId: cred.id },
|
|
481
|
+
...(cred.subjectId ? { subjectId: cred.subjectId } : {}),
|
|
473
482
|
});
|
|
474
483
|
if (winner?.status === "active") {
|
|
475
484
|
return winner;
|
|
@@ -481,7 +490,7 @@ export function buildConnectionTokenResolver(
|
|
|
481
490
|
cred: ConnectionCredentialForBroker,
|
|
482
491
|
ref: McpServerConnectionRef,
|
|
483
492
|
): Promise<ConnectionCredentialForBroker> => {
|
|
484
|
-
const key = `${cred.id}:${cred.version}`;
|
|
493
|
+
const key = `${cred.subjectId ?? "workspace"}:${cred.id}:${cred.version}`;
|
|
485
494
|
const existing = inflight.get(key);
|
|
486
495
|
if (existing) {
|
|
487
496
|
return existing;
|
|
@@ -538,6 +547,7 @@ export function buildConnectionTokenResolver(
|
|
|
538
547
|
{
|
|
539
548
|
id: cred.id,
|
|
540
549
|
version: cred.version,
|
|
550
|
+
subjectId: cred.subjectId,
|
|
541
551
|
},
|
|
542
552
|
)
|
|
543
553
|
.catch(() => undefined);
|