@opengeni/db 0.16.2 → 0.18.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.
- package/dist/{chunk-4RUK5CON.js → chunk-T6RSJT6C.js} +20 -2
- package/dist/chunk-T6RSJT6C.js.map +1 -0
- package/dist/{chunk-HZD7KVAR.js → chunk-YKWJ7QJ2.js} +369 -17
- package/dist/chunk-YKWJ7QJ2.js.map +1 -0
- package/dist/index.d.ts +225 -0
- package/dist/index.js +3735 -2488
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +3 -3
- package/dist/schema.d.ts +2631 -749
- package/dist/schema.js +15 -1
- package/dist/workspace-artifacts.d.ts +62 -0
- package/drizzle/0141_social_connection_credentials.sql +8 -0
- package/drizzle/0149_workspace_artifacts.sql +348 -0
- package/drizzle/0150_slack_task_interactions.sql +344 -0
- package/drizzle/0151_slack_delivery_backoff.sql +134 -0
- package/package.json +3 -3
- package/src/index.ts +1217 -12
- package/src/runtime-posture.ts +19 -1
- package/src/schema.ts +409 -16
- package/src/workspace-artifacts.ts +751 -0
- package/dist/chunk-4RUK5CON.js.map +0 -1
- package/dist/chunk-HZD7KVAR.js.map +0 -1
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Durable Slack-to-OpenGeni ingress, identity mapping, thread/session routing,
|
|
3
|
+
-- and delivery cursors. Signed HTTP ingress stores only bounded normalized
|
|
4
|
+
-- fields; raw bodies, headers, signing secrets, bot tokens, and response URLs
|
|
5
|
+
-- never enter these tables.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '10min';
|
|
9
|
+
|
|
10
|
+
CREATE TABLE "slack_bot_user_links" (
|
|
11
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
12
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
13
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
14
|
+
"connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
|
|
15
|
+
"slack_team_id" text NOT NULL,
|
|
16
|
+
"slack_user_id" text NOT NULL,
|
|
17
|
+
"subject_id" text NOT NULL,
|
|
18
|
+
"linked_by_subject_id" text NOT NULL,
|
|
19
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
20
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
21
|
+
CONSTRAINT "slack_bot_user_links_connection_user_uq"
|
|
22
|
+
UNIQUE ("connection_id", "slack_user_id"),
|
|
23
|
+
CONSTRAINT "slack_bot_user_links_identity_check"
|
|
24
|
+
CHECK (
|
|
25
|
+
octet_length("slack_team_id") BETWEEN 1 AND 64
|
|
26
|
+
AND octet_length("slack_user_id") BETWEEN 1 AND 64
|
|
27
|
+
AND octet_length("subject_id") BETWEEN 1 AND 1024
|
|
28
|
+
AND octet_length("linked_by_subject_id") BETWEEN 1 AND 1024
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE INDEX "slack_bot_user_links_workspace_subject_idx"
|
|
33
|
+
ON "slack_bot_user_links" ("workspace_id", "subject_id");
|
|
34
|
+
|
|
35
|
+
CREATE TABLE "slack_interaction_inbox" (
|
|
36
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
37
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
38
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
39
|
+
"connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
|
|
40
|
+
"provider_event_id" text NOT NULL,
|
|
41
|
+
"provider_message_id" text NOT NULL,
|
|
42
|
+
"slack_team_id" text NOT NULL,
|
|
43
|
+
"slack_user_id" text NOT NULL,
|
|
44
|
+
"slack_channel_id" text NOT NULL,
|
|
45
|
+
"slack_message_ts" text NOT NULL,
|
|
46
|
+
"slack_thread_ts" text,
|
|
47
|
+
"trigger_kind" text NOT NULL,
|
|
48
|
+
"text" text NOT NULL,
|
|
49
|
+
"status" text NOT NULL DEFAULT 'pending',
|
|
50
|
+
"claim_holder_id" uuid,
|
|
51
|
+
"claim_expires_at" timestamptz,
|
|
52
|
+
"attempt_count" integer NOT NULL DEFAULT 0,
|
|
53
|
+
"last_error_code" text,
|
|
54
|
+
"processed_at" timestamptz,
|
|
55
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
56
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
57
|
+
CONSTRAINT "slack_interaction_inbox_provider_event_uq"
|
|
58
|
+
UNIQUE ("connection_id", "provider_event_id"),
|
|
59
|
+
CONSTRAINT "slack_interaction_inbox_provider_message_uq"
|
|
60
|
+
UNIQUE ("connection_id", "provider_message_id"),
|
|
61
|
+
CONSTRAINT "slack_interaction_inbox_bounds_check"
|
|
62
|
+
CHECK (
|
|
63
|
+
octet_length("provider_event_id") BETWEEN 1 AND 256
|
|
64
|
+
AND octet_length("provider_message_id") BETWEEN 1 AND 256
|
|
65
|
+
AND octet_length("slack_team_id") BETWEEN 1 AND 64
|
|
66
|
+
AND octet_length("slack_user_id") BETWEEN 1 AND 64
|
|
67
|
+
AND octet_length("slack_channel_id") BETWEEN 1 AND 64
|
|
68
|
+
AND octet_length("slack_message_ts") BETWEEN 1 AND 64
|
|
69
|
+
AND ("slack_thread_ts" IS NULL OR octet_length("slack_thread_ts") BETWEEN 1 AND 64)
|
|
70
|
+
AND octet_length("text") BETWEEN 1 AND 12000
|
|
71
|
+
AND ("last_error_code" IS NULL OR octet_length("last_error_code") BETWEEN 1 AND 128)
|
|
72
|
+
),
|
|
73
|
+
CONSTRAINT "slack_interaction_inbox_trigger_check"
|
|
74
|
+
CHECK ("trigger_kind" IN ('app_mention', 'dm', 'slash_command', 'message_shortcut', 'thread_reply')),
|
|
75
|
+
CONSTRAINT "slack_interaction_inbox_status_check"
|
|
76
|
+
CHECK ("status" IN ('pending', 'processing', 'processed', 'failed')),
|
|
77
|
+
CONSTRAINT "slack_interaction_inbox_claim_check"
|
|
78
|
+
CHECK (("claim_holder_id" IS NULL) = ("claim_expires_at" IS NULL)),
|
|
79
|
+
CONSTRAINT "slack_interaction_inbox_completion_check"
|
|
80
|
+
CHECK (("status" IN ('processed', 'failed')) = ("processed_at" IS NOT NULL))
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX "slack_interaction_inbox_pending_idx"
|
|
84
|
+
ON "slack_interaction_inbox" ("status", "created_at", "id")
|
|
85
|
+
WHERE "status" IN ('pending', 'processing');
|
|
86
|
+
|
|
87
|
+
CREATE TABLE "slack_interactions" (
|
|
88
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
89
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
90
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
91
|
+
"connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
|
|
92
|
+
"slack_team_id" text NOT NULL,
|
|
93
|
+
"slack_channel_id" text NOT NULL,
|
|
94
|
+
"slack_thread_ts" text NOT NULL,
|
|
95
|
+
"route_key" text NOT NULL,
|
|
96
|
+
"triggering_provider_event_id" text NOT NULL,
|
|
97
|
+
"owning_subject_id" text NOT NULL,
|
|
98
|
+
"visibility" text NOT NULL,
|
|
99
|
+
"session_reservation_id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
100
|
+
"session_id" uuid REFERENCES "sessions"("id") ON DELETE CASCADE,
|
|
101
|
+
"last_delivered_session_event_sequence" integer NOT NULL DEFAULT 0,
|
|
102
|
+
"delivery_claim_holder_id" uuid,
|
|
103
|
+
"delivery_claim_expires_at" timestamptz,
|
|
104
|
+
"ack_slack_message_ts" text,
|
|
105
|
+
"progress_count" integer NOT NULL DEFAULT 0,
|
|
106
|
+
"terminal_delivery_state" text NOT NULL DEFAULT 'open',
|
|
107
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
108
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
109
|
+
CONSTRAINT "slack_interactions_route_uq" UNIQUE ("connection_id", "route_key"),
|
|
110
|
+
CONSTRAINT "slack_interactions_identity_uq" UNIQUE ("account_id", "workspace_id", "id"),
|
|
111
|
+
CONSTRAINT "slack_interactions_bounds_check"
|
|
112
|
+
CHECK (
|
|
113
|
+
octet_length("slack_team_id") BETWEEN 1 AND 64
|
|
114
|
+
AND octet_length("slack_channel_id") BETWEEN 1 AND 64
|
|
115
|
+
AND octet_length("slack_thread_ts") BETWEEN 1 AND 64
|
|
116
|
+
AND octet_length("route_key") BETWEEN 1 AND 256
|
|
117
|
+
AND octet_length("triggering_provider_event_id") BETWEEN 1 AND 256
|
|
118
|
+
AND octet_length("owning_subject_id") BETWEEN 1 AND 1024
|
|
119
|
+
AND ("ack_slack_message_ts" IS NULL OR octet_length("ack_slack_message_ts") BETWEEN 1 AND 64)
|
|
120
|
+
AND "last_delivered_session_event_sequence" >= 0
|
|
121
|
+
AND "progress_count" BETWEEN 0 AND 3
|
|
122
|
+
),
|
|
123
|
+
CONSTRAINT "slack_interactions_session_binding_check"
|
|
124
|
+
CHECK ("session_id" IS NULL OR "session_id" = "session_reservation_id"),
|
|
125
|
+
CONSTRAINT "slack_interactions_delivery_claim_check"
|
|
126
|
+
CHECK (("delivery_claim_holder_id" IS NULL) = ("delivery_claim_expires_at" IS NULL)),
|
|
127
|
+
CONSTRAINT "slack_interactions_visibility_check"
|
|
128
|
+
CHECK ("visibility" IN ('private', 'workspace')),
|
|
129
|
+
CONSTRAINT "slack_interactions_terminal_check"
|
|
130
|
+
CHECK ("terminal_delivery_state" IN ('open', 'completed', 'failed', 'cancelled', 'blocked'))
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
CREATE UNIQUE INDEX "slack_interactions_workspace_reservation_uq"
|
|
134
|
+
ON "slack_interactions" ("workspace_id", "session_reservation_id");
|
|
135
|
+
CREATE UNIQUE INDEX "slack_interactions_workspace_session_uq"
|
|
136
|
+
ON "slack_interactions" ("workspace_id", "session_id")
|
|
137
|
+
WHERE "session_id" IS NOT NULL;
|
|
138
|
+
CREATE INDEX "slack_interactions_delivery_idx"
|
|
139
|
+
ON "slack_interactions" ("terminal_delivery_state", "updated_at", "id")
|
|
140
|
+
WHERE "session_id" IS NOT NULL AND "terminal_delivery_state" = 'open';
|
|
141
|
+
|
|
142
|
+
CREATE TABLE "slack_interaction_progress_deliveries" (
|
|
143
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
144
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
145
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
146
|
+
"interaction_id" uuid NOT NULL,
|
|
147
|
+
"session_event_sequence" integer NOT NULL,
|
|
148
|
+
"slot" integer NOT NULL,
|
|
149
|
+
"operation_id" uuid NOT NULL,
|
|
150
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
151
|
+
CONSTRAINT "slack_interaction_progress_deliveries_interaction_fk"
|
|
152
|
+
FOREIGN KEY ("account_id", "workspace_id", "interaction_id")
|
|
153
|
+
REFERENCES "slack_interactions" ("account_id", "workspace_id", "id")
|
|
154
|
+
ON DELETE CASCADE,
|
|
155
|
+
CONSTRAINT "slack_interaction_progress_deliveries_event_uq"
|
|
156
|
+
UNIQUE ("interaction_id", "session_event_sequence"),
|
|
157
|
+
CONSTRAINT "slack_interaction_progress_deliveries_slot_uq"
|
|
158
|
+
UNIQUE ("interaction_id", "slot"),
|
|
159
|
+
CONSTRAINT "slack_interaction_progress_deliveries_operation_uq"
|
|
160
|
+
UNIQUE ("workspace_id", "operation_id"),
|
|
161
|
+
CONSTRAINT "slack_interaction_progress_deliveries_bounds_check"
|
|
162
|
+
CHECK ("session_event_sequence" > 0 AND "slot" BETWEEN 1 AND 3)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
ALTER TABLE "slack_bot_user_links" ENABLE ROW LEVEL SECURITY;
|
|
166
|
+
ALTER TABLE "slack_bot_user_links" FORCE ROW LEVEL SECURITY;
|
|
167
|
+
ALTER TABLE "slack_interaction_inbox" ENABLE ROW LEVEL SECURITY;
|
|
168
|
+
ALTER TABLE "slack_interaction_inbox" FORCE ROW LEVEL SECURITY;
|
|
169
|
+
ALTER TABLE "slack_interactions" ENABLE ROW LEVEL SECURITY;
|
|
170
|
+
ALTER TABLE "slack_interactions" FORCE ROW LEVEL SECURITY;
|
|
171
|
+
ALTER TABLE "slack_interaction_progress_deliveries" ENABLE ROW LEVEL SECURITY;
|
|
172
|
+
ALTER TABLE "slack_interaction_progress_deliveries" FORCE ROW LEVEL SECURITY;
|
|
173
|
+
|
|
174
|
+
CREATE POLICY workspace_isolation ON "slack_bot_user_links"
|
|
175
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
176
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
177
|
+
CREATE POLICY workspace_isolation ON "slack_interaction_inbox"
|
|
178
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
179
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
180
|
+
CREATE POLICY workspace_isolation ON "slack_interactions"
|
|
181
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
182
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
183
|
+
CREATE POLICY workspace_isolation ON "slack_interaction_progress_deliveries"
|
|
184
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
185
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
186
|
+
|
|
187
|
+
-- Resolve and claim through SECURITY DEFINER without trusting the caller's
|
|
188
|
+
-- search_path (including pg_temp). The migration may target public or a
|
|
189
|
+
-- dedicated embed schema, so bind current_schema() into every relation and
|
|
190
|
+
-- return type at creation time, then expose only pg_catalog at execution time.
|
|
191
|
+
DO $privileged_functions$
|
|
192
|
+
DECLARE data_schema text := current_schema();
|
|
193
|
+
BEGIN
|
|
194
|
+
EXECUTE format($ddl$
|
|
195
|
+
CREATE OR REPLACE FUNCTION opengeni_private.resolve_slack_installation(p_team_id text)
|
|
196
|
+
RETURNS TABLE (
|
|
197
|
+
account_id uuid,
|
|
198
|
+
workspace_id uuid,
|
|
199
|
+
connection_id uuid,
|
|
200
|
+
bot_id text,
|
|
201
|
+
bot_user_id text
|
|
202
|
+
)
|
|
203
|
+
LANGUAGE sql
|
|
204
|
+
SECURITY DEFINER
|
|
205
|
+
SET search_path = pg_catalog
|
|
206
|
+
AS $function$
|
|
207
|
+
WITH eligible AS (
|
|
208
|
+
SELECT
|
|
209
|
+
C.account_id,
|
|
210
|
+
C.workspace_id,
|
|
211
|
+
C.id AS connection_id,
|
|
212
|
+
C.metadata->>'botId' AS bot_id,
|
|
213
|
+
C.metadata->>'botUserId' AS bot_user_id,
|
|
214
|
+
C.created_at
|
|
215
|
+
FROM %1$I.connections C
|
|
216
|
+
WHERE C.provider_domain = 'slack.com'
|
|
217
|
+
AND C.kind = 'app_install'
|
|
218
|
+
AND C.subject_id IS NULL
|
|
219
|
+
AND C.status = 'active'
|
|
220
|
+
AND C.verified_install_at IS NOT NULL
|
|
221
|
+
AND C.verified_install_version = C.version
|
|
222
|
+
AND C.metadata->>'credentialRole' = 'opengeni_slack_bot'
|
|
223
|
+
AND C.metadata->>'slackTeamId' = p_team_id
|
|
224
|
+
AND octet_length(C.metadata->>'botId') BETWEEN 1 AND 64
|
|
225
|
+
AND octet_length(C.metadata->>'botUserId') BETWEEN 1 AND 64
|
|
226
|
+
), unambiguous AS (
|
|
227
|
+
SELECT count(DISTINCT (workspace_id, bot_id, bot_user_id)) AS principal_count
|
|
228
|
+
FROM eligible
|
|
229
|
+
)
|
|
230
|
+
SELECT E.account_id, E.workspace_id, E.connection_id, E.bot_id, E.bot_user_id
|
|
231
|
+
FROM eligible E, unambiguous U
|
|
232
|
+
WHERE U.principal_count = 1
|
|
233
|
+
ORDER BY E.created_at DESC, E.connection_id DESC
|
|
234
|
+
LIMIT 1
|
|
235
|
+
$function$
|
|
236
|
+
$ddl$, data_schema);
|
|
237
|
+
|
|
238
|
+
EXECUTE format($ddl$
|
|
239
|
+
CREATE OR REPLACE FUNCTION opengeni_private.claim_slack_interaction_inbox(
|
|
240
|
+
p_holder uuid,
|
|
241
|
+
p_lease_ms integer
|
|
242
|
+
)
|
|
243
|
+
RETURNS SETOF %1$I.slack_interaction_inbox
|
|
244
|
+
LANGUAGE plpgsql
|
|
245
|
+
SECURITY DEFINER
|
|
246
|
+
SET search_path = pg_catalog
|
|
247
|
+
AS $function$
|
|
248
|
+
BEGIN
|
|
249
|
+
IF p_lease_ms < 1000 OR p_lease_ms > 300000 THEN
|
|
250
|
+
RAISE EXCEPTION 'invalid Slack inbox claim lease';
|
|
251
|
+
END IF;
|
|
252
|
+
RETURN QUERY
|
|
253
|
+
WITH candidate AS (
|
|
254
|
+
SELECT I.id
|
|
255
|
+
FROM %1$I.slack_interaction_inbox I
|
|
256
|
+
WHERE I.status = 'pending'
|
|
257
|
+
OR (I.status = 'processing' AND I.claim_expires_at <= now())
|
|
258
|
+
ORDER BY I.created_at, I.id
|
|
259
|
+
FOR UPDATE SKIP LOCKED
|
|
260
|
+
LIMIT 1
|
|
261
|
+
)
|
|
262
|
+
UPDATE %1$I.slack_interaction_inbox I
|
|
263
|
+
SET status = 'processing',
|
|
264
|
+
claim_holder_id = p_holder,
|
|
265
|
+
claim_expires_at = now() + make_interval(secs => p_lease_ms::double precision / 1000),
|
|
266
|
+
attempt_count = I.attempt_count + 1,
|
|
267
|
+
updated_at = now()
|
|
268
|
+
FROM candidate C
|
|
269
|
+
WHERE I.id = C.id
|
|
270
|
+
RETURNING I.*;
|
|
271
|
+
END
|
|
272
|
+
$function$
|
|
273
|
+
$ddl$, data_schema);
|
|
274
|
+
|
|
275
|
+
EXECUTE format($ddl$
|
|
276
|
+
CREATE OR REPLACE FUNCTION opengeni_private.claim_slack_interaction_delivery(
|
|
277
|
+
p_holder uuid,
|
|
278
|
+
p_lease_ms integer
|
|
279
|
+
)
|
|
280
|
+
RETURNS SETOF %1$I.slack_interactions
|
|
281
|
+
LANGUAGE plpgsql
|
|
282
|
+
SECURITY DEFINER
|
|
283
|
+
SET search_path = pg_catalog
|
|
284
|
+
AS $function$
|
|
285
|
+
BEGIN
|
|
286
|
+
IF p_lease_ms < 1000 OR p_lease_ms > 300000 THEN
|
|
287
|
+
RAISE EXCEPTION 'invalid Slack delivery claim lease';
|
|
288
|
+
END IF;
|
|
289
|
+
RETURN QUERY
|
|
290
|
+
WITH candidate AS (
|
|
291
|
+
SELECT I.id
|
|
292
|
+
FROM %1$I.slack_interactions I
|
|
293
|
+
WHERE I.session_id IS NOT NULL
|
|
294
|
+
AND I.terminal_delivery_state = 'open'
|
|
295
|
+
AND (I.delivery_claim_holder_id IS NULL OR I.delivery_claim_expires_at <= now())
|
|
296
|
+
AND EXISTS (
|
|
297
|
+
SELECT 1
|
|
298
|
+
FROM %1$I.session_events E
|
|
299
|
+
WHERE E.workspace_id = I.workspace_id
|
|
300
|
+
AND E.session_id = I.session_id
|
|
301
|
+
AND E.sequence > I.last_delivered_session_event_sequence
|
|
302
|
+
AND E.type IN (
|
|
303
|
+
'agent.message.completed',
|
|
304
|
+
'session.humanInput.requested',
|
|
305
|
+
'turn.completed',
|
|
306
|
+
'turn.failed',
|
|
307
|
+
'turn.cancelled',
|
|
308
|
+
'session.status.changed'
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
ORDER BY I.updated_at, I.id
|
|
312
|
+
FOR UPDATE SKIP LOCKED
|
|
313
|
+
LIMIT 1
|
|
314
|
+
)
|
|
315
|
+
UPDATE %1$I.slack_interactions I
|
|
316
|
+
SET delivery_claim_holder_id = p_holder,
|
|
317
|
+
delivery_claim_expires_at = now() + make_interval(secs => p_lease_ms::double precision / 1000),
|
|
318
|
+
updated_at = now()
|
|
319
|
+
FROM candidate C
|
|
320
|
+
WHERE I.id = C.id
|
|
321
|
+
RETURNING I.*;
|
|
322
|
+
END
|
|
323
|
+
$function$
|
|
324
|
+
$ddl$, data_schema);
|
|
325
|
+
END
|
|
326
|
+
$privileged_functions$;
|
|
327
|
+
|
|
328
|
+
DO $grants$
|
|
329
|
+
DECLARE target_schema text := current_schema();
|
|
330
|
+
BEGIN
|
|
331
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
332
|
+
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.slack_bot_user_links TO opengeni_app', target_schema);
|
|
333
|
+
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.slack_interaction_inbox TO opengeni_app', target_schema);
|
|
334
|
+
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.slack_interactions TO opengeni_app', target_schema);
|
|
335
|
+
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.slack_interaction_progress_deliveries TO opengeni_app', target_schema);
|
|
336
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.resolve_slack_installation(text) TO opengeni_app;
|
|
337
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.claim_slack_interaction_inbox(uuid, integer) TO opengeni_app;
|
|
338
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.claim_slack_interaction_delivery(uuid, integer) TO opengeni_app;
|
|
339
|
+
END IF;
|
|
340
|
+
END
|
|
341
|
+
$grants$;
|
|
342
|
+
|
|
343
|
+
RESET statement_timeout;
|
|
344
|
+
RESET lock_timeout;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
SET lock_timeout = '5s';
|
|
3
|
+
SET statement_timeout = '30s';
|
|
4
|
+
|
|
5
|
+
ALTER TABLE slack_interactions
|
|
6
|
+
ADD COLUMN delivery_attempt_count integer NOT NULL DEFAULT 0,
|
|
7
|
+
ADD COLUMN delivery_retry_at timestamptz,
|
|
8
|
+
ADD COLUMN delivery_last_error_code text;
|
|
9
|
+
|
|
10
|
+
ALTER TABLE slack_interactions
|
|
11
|
+
ADD CONSTRAINT slack_interactions_delivery_attempt_check
|
|
12
|
+
CHECK (delivery_attempt_count >= 0),
|
|
13
|
+
ADD CONSTRAINT slack_interactions_delivery_error_check
|
|
14
|
+
CHECK (
|
|
15
|
+
delivery_last_error_code IS NULL
|
|
16
|
+
OR octet_length(delivery_last_error_code) BETWEEN 1 AND 128
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
ALTER TABLE slack_interaction_inbox
|
|
20
|
+
ADD COLUMN retry_at timestamptz;
|
|
21
|
+
|
|
22
|
+
DROP INDEX slack_interaction_inbox_pending_idx;
|
|
23
|
+
CREATE INDEX slack_interaction_inbox_pending_idx
|
|
24
|
+
ON slack_interaction_inbox (status, retry_at, created_at, id)
|
|
25
|
+
WHERE status IN ('pending', 'processing');
|
|
26
|
+
|
|
27
|
+
DROP INDEX slack_interactions_delivery_idx;
|
|
28
|
+
CREATE INDEX slack_interactions_delivery_idx
|
|
29
|
+
ON slack_interactions (terminal_delivery_state, delivery_retry_at, updated_at, id)
|
|
30
|
+
WHERE session_id IS NOT NULL AND terminal_delivery_state = 'open';
|
|
31
|
+
|
|
32
|
+
DO $privileged_functions$
|
|
33
|
+
DECLARE data_schema text := current_schema();
|
|
34
|
+
BEGIN
|
|
35
|
+
EXECUTE format($ddl$
|
|
36
|
+
CREATE OR REPLACE FUNCTION opengeni_private.claim_slack_interaction_inbox(
|
|
37
|
+
p_holder uuid,
|
|
38
|
+
p_lease_ms integer
|
|
39
|
+
)
|
|
40
|
+
RETURNS SETOF %1$I.slack_interaction_inbox
|
|
41
|
+
LANGUAGE plpgsql
|
|
42
|
+
SECURITY DEFINER
|
|
43
|
+
SET search_path = pg_catalog
|
|
44
|
+
AS $function$
|
|
45
|
+
BEGIN
|
|
46
|
+
IF p_lease_ms < 1000 OR p_lease_ms > 300000 THEN
|
|
47
|
+
RAISE EXCEPTION 'invalid Slack inbox claim lease';
|
|
48
|
+
END IF;
|
|
49
|
+
RETURN QUERY
|
|
50
|
+
WITH candidate AS (
|
|
51
|
+
SELECT I.id
|
|
52
|
+
FROM %1$I.slack_interaction_inbox I
|
|
53
|
+
WHERE (
|
|
54
|
+
I.status = 'pending'
|
|
55
|
+
AND (I.retry_at IS NULL OR I.retry_at <= now())
|
|
56
|
+
) OR (
|
|
57
|
+
I.status = 'processing'
|
|
58
|
+
AND I.claim_expires_at <= now()
|
|
59
|
+
)
|
|
60
|
+
ORDER BY I.retry_at NULLS FIRST, I.created_at, I.id
|
|
61
|
+
FOR UPDATE SKIP LOCKED
|
|
62
|
+
LIMIT 1
|
|
63
|
+
)
|
|
64
|
+
UPDATE %1$I.slack_interaction_inbox I
|
|
65
|
+
SET status = 'processing',
|
|
66
|
+
claim_holder_id = p_holder,
|
|
67
|
+
claim_expires_at = now() + make_interval(secs => p_lease_ms::double precision / 1000),
|
|
68
|
+
retry_at = NULL,
|
|
69
|
+
attempt_count = I.attempt_count + 1,
|
|
70
|
+
updated_at = now()
|
|
71
|
+
FROM candidate C
|
|
72
|
+
WHERE I.id = C.id
|
|
73
|
+
RETURNING I.*;
|
|
74
|
+
END
|
|
75
|
+
$function$
|
|
76
|
+
$ddl$, data_schema);
|
|
77
|
+
|
|
78
|
+
EXECUTE format($ddl$
|
|
79
|
+
CREATE OR REPLACE FUNCTION opengeni_private.claim_slack_interaction_delivery(
|
|
80
|
+
p_holder uuid,
|
|
81
|
+
p_lease_ms integer
|
|
82
|
+
)
|
|
83
|
+
RETURNS SETOF %1$I.slack_interactions
|
|
84
|
+
LANGUAGE plpgsql
|
|
85
|
+
SECURITY DEFINER
|
|
86
|
+
SET search_path = pg_catalog
|
|
87
|
+
AS $function$
|
|
88
|
+
BEGIN
|
|
89
|
+
IF p_lease_ms < 1000 OR p_lease_ms > 300000 THEN
|
|
90
|
+
RAISE EXCEPTION 'invalid Slack delivery claim lease';
|
|
91
|
+
END IF;
|
|
92
|
+
RETURN QUERY
|
|
93
|
+
WITH candidate AS (
|
|
94
|
+
SELECT I.id
|
|
95
|
+
FROM %1$I.slack_interactions I
|
|
96
|
+
WHERE I.session_id IS NOT NULL
|
|
97
|
+
AND I.terminal_delivery_state = 'open'
|
|
98
|
+
AND (I.delivery_retry_at IS NULL OR I.delivery_retry_at <= now())
|
|
99
|
+
AND (I.delivery_claim_holder_id IS NULL OR I.delivery_claim_expires_at <= now())
|
|
100
|
+
AND EXISTS (
|
|
101
|
+
SELECT 1
|
|
102
|
+
FROM %1$I.session_events E
|
|
103
|
+
WHERE E.workspace_id = I.workspace_id
|
|
104
|
+
AND E.session_id = I.session_id
|
|
105
|
+
AND E.sequence > I.last_delivered_session_event_sequence
|
|
106
|
+
AND E.type IN (
|
|
107
|
+
'agent.message.completed',
|
|
108
|
+
'session.humanInput.requested',
|
|
109
|
+
'turn.completed',
|
|
110
|
+
'turn.failed',
|
|
111
|
+
'turn.cancelled',
|
|
112
|
+
'session.status.changed'
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
ORDER BY I.delivery_retry_at NULLS FIRST, I.updated_at, I.id
|
|
116
|
+
FOR UPDATE SKIP LOCKED
|
|
117
|
+
LIMIT 1
|
|
118
|
+
)
|
|
119
|
+
UPDATE %1$I.slack_interactions I
|
|
120
|
+
SET delivery_claim_holder_id = p_holder,
|
|
121
|
+
delivery_claim_expires_at = now() + make_interval(secs => p_lease_ms::double precision / 1000),
|
|
122
|
+
delivery_attempt_count = I.delivery_attempt_count + 1,
|
|
123
|
+
updated_at = now()
|
|
124
|
+
FROM candidate C
|
|
125
|
+
WHERE I.id = C.id
|
|
126
|
+
RETURNING I.*;
|
|
127
|
+
END
|
|
128
|
+
$function$
|
|
129
|
+
$ddl$, data_schema);
|
|
130
|
+
END
|
|
131
|
+
$privileged_functions$;
|
|
132
|
+
|
|
133
|
+
RESET statement_timeout;
|
|
134
|
+
RESET lock_timeout;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/db",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
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.9",
|
|
54
|
-
"@opengeni/config": "^0.
|
|
55
|
-
"@opengeni/contracts": "^0.
|
|
54
|
+
"@opengeni/config": "^0.9.1",
|
|
55
|
+
"@opengeni/contracts": "^0.28.1",
|
|
56
56
|
"@opengeni/network": "^0.1.1",
|
|
57
57
|
"drizzle-orm": "^0.45.2",
|
|
58
58
|
"postgres": "^3.4.7"
|