@amalgm/automations 0.2.1 → 0.2.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/AXIOMS.md +39 -17
- package/PURPOSE.md +26 -11
- package/README.md +33 -4
- package/dist/host/config.d.ts +2 -0
- package/dist/host/config.js +8 -0
- package/dist/host/main.js +10 -5
- package/dist/host/server.d.ts +2 -0
- package/dist/host/server.js +25 -7
- package/dist/src/automations.d.ts +15 -9
- package/dist/src/automations.js +33 -76
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +5 -2
- package/dist/src/client.d.ts +1 -0
- package/dist/src/client.js +4 -2
- package/dist/src/contract.d.ts +11 -31
- package/dist/src/crud/context.d.ts +3 -1
- package/dist/src/crud/context.js +2 -1
- package/dist/src/crud/repository.d.ts +17 -6
- package/dist/src/crud/runs.d.ts +1 -1
- package/dist/src/crud/runs.js +24 -1
- package/dist/src/crud/triggers.js +18 -9
- package/dist/src/crud.d.ts +4 -2
- package/dist/src/crud.js +5 -2
- package/dist/src/events-http.d.ts +3 -5
- package/dist/src/events-http.js +22 -26
- package/dist/src/executor.d.ts +12 -3
- package/dist/src/executor.js +163 -44
- package/dist/src/http.js +8 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +6 -4
- package/dist/src/machine-client.d.ts +1 -0
- package/dist/src/machine-client.js +2 -0
- package/dist/src/machine.d.ts +2 -1
- package/dist/src/machine.js +6 -1
- package/dist/src/mcp.d.ts +3 -0
- package/dist/src/mcp.js +61 -50
- package/dist/src/plan.d.ts +3 -0
- package/dist/src/plan.js +48 -0
- package/dist/src/run-contract.d.ts +52 -0
- package/dist/src/run-contract.js +1 -0
- package/dist/src/run-journal.d.ts +8 -0
- package/dist/src/run-journal.js +56 -0
- package/dist/src/runner.d.ts +14 -0
- package/dist/src/runner.js +70 -0
- package/dist/src/schema.d.ts +31 -15
- package/dist/src/schema.js +27 -4
- package/dist/src/supabase-crud/mappers.d.ts +3 -2
- package/dist/src/supabase-crud/mappers.js +5 -0
- package/dist/src/supabase-crud/rows.d.ts +3 -0
- package/dist/src/supabase-crud/workflow-runs.d.ts +1 -1
- package/dist/src/supabase-crud/workflow-runs.js +10 -0
- package/dist/src/supabase-machine.js +2 -0
- package/dist/src/supabase-store.d.ts +3 -6
- package/dist/src/supabase-store.js +13 -34
- package/dist/src/tool-surface.d.ts +47 -0
- package/dist/src/tool-surface.js +126 -0
- package/dist/src/types.d.ts +8 -24
- package/dist/src/webhook.d.ts +15 -1
- package/dist/src/webhook.js +86 -0
- package/package.json +3 -3
- package/skills/automations/SKILL.md +29 -16
- package/supabase/migrations/20260830010000_durable_step_retries.sql +332 -0
- package/supabase/migrations/20260831010000_public_webhook_endpoints.sql +287 -0
- package/supabase/migrations/20260831020000_manual_run_admission.sql +68 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
-- One Supabase run row is the queue, lease, durable step journal, and history.
|
|
2
|
+
-- The target machine is the only executor.
|
|
3
|
+
|
|
4
|
+
ALTER TABLE public.amalgm_automation_runs
|
|
5
|
+
ADD COLUMN IF NOT EXISTS retry_at timestamptz;
|
|
6
|
+
|
|
7
|
+
DROP FUNCTION IF EXISTS list_amalgm_pending_runs(uuid, text);
|
|
8
|
+
DROP FUNCTION IF EXISTS mark_amalgm_run_sent(uuid, uuid, text, timestamptz);
|
|
9
|
+
DROP FUNCTION IF EXISTS update_amalgm_run(uuid, uuid, text, jsonb);
|
|
10
|
+
|
|
11
|
+
ALTER TABLE public.amalgm_automation_runs
|
|
12
|
+
DROP CONSTRAINT IF EXISTS amalgm_automation_run_retry_state;
|
|
13
|
+
ALTER TABLE public.amalgm_automation_runs
|
|
14
|
+
ADD CONSTRAINT amalgm_automation_run_retry_state CHECK (
|
|
15
|
+
status = 'pending' OR retry_at IS NULL
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
DROP INDEX IF EXISTS public.amalgm_automation_runs_claimable;
|
|
19
|
+
CREATE INDEX amalgm_automation_runs_claimable
|
|
20
|
+
ON public.amalgm_automation_runs(user_id, target_id, status, retry_at, lease_expires_at, created_at, id)
|
|
21
|
+
WHERE status IN ('pending', 'sent', 'running');
|
|
22
|
+
|
|
23
|
+
CREATE OR REPLACE FUNCTION list_amalgm_event_triggers(p_user_id uuid, p_target_id text)
|
|
24
|
+
RETURNS TABLE (
|
|
25
|
+
user_id uuid,
|
|
26
|
+
automation_id text,
|
|
27
|
+
trigger_id text,
|
|
28
|
+
target_id text,
|
|
29
|
+
source text,
|
|
30
|
+
event text,
|
|
31
|
+
secret text
|
|
32
|
+
)
|
|
33
|
+
LANGUAGE sql
|
|
34
|
+
SECURITY DEFINER
|
|
35
|
+
SET search_path = public
|
|
36
|
+
AS $$
|
|
37
|
+
SELECT t.user_id, t.automation_id, t.id, a.target_id, t.source, t.event, t.secret
|
|
38
|
+
FROM public.amalgm_automation_triggers t
|
|
39
|
+
JOIN public.amalgm_automations a
|
|
40
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
41
|
+
JOIN public.amalgm_automation_workflows w
|
|
42
|
+
ON w.user_id = a.user_id AND w.automation_id = a.id
|
|
43
|
+
WHERE t.kind = 'webhook' AND t.user_id = p_user_id
|
|
44
|
+
AND a.target_id = p_target_id AND t.enabled AND a.enabled
|
|
45
|
+
AND jsonb_typeof(w.compiled) = 'object'
|
|
46
|
+
AND w.compiled->>'version' = '1'
|
|
47
|
+
AND jsonb_typeof(w.compiled->'steps') = 'array'
|
|
48
|
+
AND jsonb_array_length(
|
|
49
|
+
CASE WHEN jsonb_typeof(w.compiled->'steps') = 'array'
|
|
50
|
+
THEN w.compiled->'steps' ELSE '[]'::jsonb END
|
|
51
|
+
) BETWEEN 1 AND 100;
|
|
52
|
+
$$;
|
|
53
|
+
|
|
54
|
+
DROP FUNCTION IF EXISTS list_due_amalgm_crons(timestamptz);
|
|
55
|
+
CREATE FUNCTION list_due_amalgm_crons(p_now timestamptz)
|
|
56
|
+
RETURNS TABLE (
|
|
57
|
+
user_id uuid,
|
|
58
|
+
automation_id text,
|
|
59
|
+
trigger_id text,
|
|
60
|
+
target_id text,
|
|
61
|
+
cron text,
|
|
62
|
+
timezone text,
|
|
63
|
+
next_run_at timestamptz,
|
|
64
|
+
remaining_occurrences integer
|
|
65
|
+
)
|
|
66
|
+
LANGUAGE sql
|
|
67
|
+
SECURITY DEFINER
|
|
68
|
+
SET search_path = public
|
|
69
|
+
AS $$
|
|
70
|
+
SELECT t.user_id, t.automation_id, t.id, a.target_id,
|
|
71
|
+
t.cron, t.timezone, t.next_run_at, t.remaining_occurrences
|
|
72
|
+
FROM public.amalgm_automation_triggers t
|
|
73
|
+
JOIN public.amalgm_automations a
|
|
74
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
75
|
+
JOIN public.amalgm_automation_workflows w
|
|
76
|
+
ON w.user_id = a.user_id AND w.automation_id = a.id
|
|
77
|
+
WHERE t.kind = 'schedule' AND t.next_run_at <= p_now
|
|
78
|
+
AND (t.remaining_occurrences IS NULL OR t.remaining_occurrences > 0)
|
|
79
|
+
AND t.enabled AND a.enabled
|
|
80
|
+
AND jsonb_typeof(w.compiled) = 'object'
|
|
81
|
+
AND w.compiled->>'version' = '1'
|
|
82
|
+
AND jsonb_typeof(w.compiled->'steps') = 'array'
|
|
83
|
+
AND jsonb_array_length(
|
|
84
|
+
CASE WHEN jsonb_typeof(w.compiled->'steps') = 'array'
|
|
85
|
+
THEN w.compiled->'steps' ELSE '[]'::jsonb END
|
|
86
|
+
) BETWEEN 1 AND 100
|
|
87
|
+
ORDER BY t.next_run_at, t.user_id, t.automation_id, t.id;
|
|
88
|
+
$$;
|
|
89
|
+
|
|
90
|
+
-- Re-check the executable plan inside the same transaction that admits work.
|
|
91
|
+
-- Discovery is advisory; this function is the authority at the race boundary.
|
|
92
|
+
CREATE OR REPLACE FUNCTION enqueue_amalgm_run(
|
|
93
|
+
p_kind text,
|
|
94
|
+
p_user_id uuid,
|
|
95
|
+
p_automation_id text,
|
|
96
|
+
p_trigger_id text,
|
|
97
|
+
p_target_id text,
|
|
98
|
+
p_expected_next_run_at timestamptz,
|
|
99
|
+
p_next_run_at timestamptz,
|
|
100
|
+
p_input jsonb,
|
|
101
|
+
p_now timestamptz
|
|
102
|
+
) RETURNS SETOF public.amalgm_automation_runs
|
|
103
|
+
LANGUAGE plpgsql
|
|
104
|
+
SECURITY DEFINER
|
|
105
|
+
SET search_path = public
|
|
106
|
+
AS $$
|
|
107
|
+
DECLARE
|
|
108
|
+
v_inserted integer := 0;
|
|
109
|
+
BEGIN
|
|
110
|
+
IF p_kind = 'cron' THEN
|
|
111
|
+
PERFORM 1
|
|
112
|
+
FROM public.amalgm_automation_triggers t
|
|
113
|
+
JOIN public.amalgm_automations a
|
|
114
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
115
|
+
WHERE t.user_id = p_user_id AND t.automation_id = p_automation_id
|
|
116
|
+
AND t.id = p_trigger_id AND a.target_id = p_target_id
|
|
117
|
+
AND t.kind = 'schedule' AND t.next_run_at = p_expected_next_run_at
|
|
118
|
+
AND (t.remaining_occurrences IS NULL OR t.remaining_occurrences > 0)
|
|
119
|
+
AND t.enabled AND a.enabled
|
|
120
|
+
FOR UPDATE OF t;
|
|
121
|
+
ELSIF p_kind = 'event' THEN
|
|
122
|
+
PERFORM 1
|
|
123
|
+
FROM public.amalgm_automation_triggers t
|
|
124
|
+
JOIN public.amalgm_automations a
|
|
125
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
126
|
+
WHERE t.user_id = p_user_id AND t.automation_id = p_automation_id
|
|
127
|
+
AND t.id = p_trigger_id AND a.target_id = p_target_id
|
|
128
|
+
AND t.kind = 'webhook' AND t.enabled AND a.enabled;
|
|
129
|
+
ELSE
|
|
130
|
+
RAISE EXCEPTION 'Invalid automation trigger kind';
|
|
131
|
+
END IF;
|
|
132
|
+
IF NOT FOUND THEN RETURN; END IF;
|
|
133
|
+
|
|
134
|
+
RETURN QUERY
|
|
135
|
+
INSERT INTO public.amalgm_automation_runs (
|
|
136
|
+
user_id, target_id, automation_id, trigger_id, workflow_id,
|
|
137
|
+
automation_payload, input, status, created_at
|
|
138
|
+
)
|
|
139
|
+
SELECT a.user_id, a.target_id, a.id, t.id, w.id,
|
|
140
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
141
|
+
'id', a.id, 'targetId', a.target_id, 'name', a.name,
|
|
142
|
+
'description', a.description, 'enabled', a.enabled,
|
|
143
|
+
'trigger', jsonb_strip_nulls(jsonb_build_object(
|
|
144
|
+
'id', t.id,
|
|
145
|
+
'kind', CASE WHEN t.kind = 'schedule' THEN 'cron' ELSE 'event' END,
|
|
146
|
+
'enabled', t.enabled, 'cron', t.cron, 'timezone', t.timezone,
|
|
147
|
+
'source', t.source, 'event', t.event
|
|
148
|
+
)),
|
|
149
|
+
'workflow', jsonb_strip_nulls(jsonb_build_object(
|
|
150
|
+
'id', w.id, 'name', w.name, 'script', w.script,
|
|
151
|
+
'compiled', w.compiled, 'allowlist', w.allowlist, 'limits', w.limits
|
|
152
|
+
))
|
|
153
|
+
)),
|
|
154
|
+
p_input, 'pending', p_now
|
|
155
|
+
FROM public.amalgm_automations a
|
|
156
|
+
JOIN public.amalgm_automation_triggers t
|
|
157
|
+
ON t.user_id = a.user_id AND t.automation_id = a.id
|
|
158
|
+
JOIN public.amalgm_automation_workflows w
|
|
159
|
+
ON w.user_id = a.user_id AND w.automation_id = a.id
|
|
160
|
+
WHERE a.user_id = p_user_id AND a.id = p_automation_id AND t.id = p_trigger_id
|
|
161
|
+
AND a.target_id = p_target_id AND t.enabled AND a.enabled
|
|
162
|
+
AND jsonb_typeof(w.compiled) = 'object'
|
|
163
|
+
AND w.compiled->>'version' = '1'
|
|
164
|
+
AND jsonb_typeof(w.compiled->'steps') = 'array'
|
|
165
|
+
AND jsonb_array_length(
|
|
166
|
+
CASE WHEN jsonb_typeof(w.compiled->'steps') = 'array'
|
|
167
|
+
THEN w.compiled->'steps' ELSE '[]'::jsonb END
|
|
168
|
+
) BETWEEN 1 AND 100
|
|
169
|
+
RETURNING *;
|
|
170
|
+
GET DIAGNOSTICS v_inserted = ROW_COUNT;
|
|
171
|
+
|
|
172
|
+
IF p_kind = 'cron' AND v_inserted = 1 THEN
|
|
173
|
+
UPDATE public.amalgm_automation_triggers SET
|
|
174
|
+
next_run_at = p_next_run_at,
|
|
175
|
+
remaining_occurrences = CASE WHEN remaining_occurrences IS NULL
|
|
176
|
+
THEN NULL ELSE remaining_occurrences - 1 END,
|
|
177
|
+
enabled = CASE WHEN remaining_occurrences = 1 THEN false ELSE enabled END,
|
|
178
|
+
updated_at = p_now
|
|
179
|
+
WHERE user_id = p_user_id AND automation_id = p_automation_id
|
|
180
|
+
AND id = p_trigger_id AND kind = 'schedule'
|
|
181
|
+
AND next_run_at = p_expected_next_run_at;
|
|
182
|
+
END IF;
|
|
183
|
+
END;
|
|
184
|
+
$$;
|
|
185
|
+
|
|
186
|
+
CREATE OR REPLACE FUNCTION claim_amalgm_machine_runs(
|
|
187
|
+
p_user_id uuid,
|
|
188
|
+
p_target_id text,
|
|
189
|
+
p_limit integer,
|
|
190
|
+
p_lease_seconds integer
|
|
191
|
+
) RETURNS SETOF public.amalgm_automation_runs
|
|
192
|
+
LANGUAGE plpgsql
|
|
193
|
+
SECURITY DEFINER
|
|
194
|
+
SET search_path = public
|
|
195
|
+
AS $$
|
|
196
|
+
BEGIN
|
|
197
|
+
UPDATE public.amalgm_automation_runs SET
|
|
198
|
+
status = 'failed', error = COALESCE(error, 'Machine execution attempts exhausted'),
|
|
199
|
+
finished_at = now(), lease_token = NULL, lease_expires_at = NULL, retry_at = NULL
|
|
200
|
+
WHERE user_id = p_user_id AND target_id = p_target_id AND attempts >= 5
|
|
201
|
+
AND (
|
|
202
|
+
(status = 'pending' AND COALESCE(retry_at, '-infinity'::timestamptz) <= now())
|
|
203
|
+
OR (status IN ('sent', 'running') AND COALESCE(lease_expires_at, '-infinity'::timestamptz) <= now())
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
RETURN QUERY
|
|
207
|
+
WITH candidates AS (
|
|
208
|
+
SELECT id
|
|
209
|
+
FROM public.amalgm_automation_runs
|
|
210
|
+
WHERE user_id = p_user_id AND target_id = p_target_id AND attempts < 5
|
|
211
|
+
AND (
|
|
212
|
+
(status = 'pending' AND COALESCE(retry_at, '-infinity'::timestamptz) <= now())
|
|
213
|
+
OR (status IN ('sent', 'running') AND COALESCE(lease_expires_at, '-infinity'::timestamptz) <= now())
|
|
214
|
+
)
|
|
215
|
+
ORDER BY created_at, id
|
|
216
|
+
FOR UPDATE SKIP LOCKED
|
|
217
|
+
LIMIT LEAST(GREATEST(p_limit, 1), 20)
|
|
218
|
+
)
|
|
219
|
+
UPDATE public.amalgm_automation_runs r SET
|
|
220
|
+
status = 'sent', sent_at = COALESCE(sent_at, now()),
|
|
221
|
+
lease_token = gen_random_uuid(),
|
|
222
|
+
lease_expires_at = now() + make_interval(secs => LEAST(GREATEST(p_lease_seconds, 30), 900)),
|
|
223
|
+
retry_at = NULL,
|
|
224
|
+
attempts = attempts + 1
|
|
225
|
+
FROM candidates
|
|
226
|
+
WHERE r.id = candidates.id
|
|
227
|
+
RETURNING r.*;
|
|
228
|
+
END;
|
|
229
|
+
$$;
|
|
230
|
+
|
|
231
|
+
CREATE OR REPLACE FUNCTION update_amalgm_claimed_run(
|
|
232
|
+
p_user_id uuid,
|
|
233
|
+
p_target_id text,
|
|
234
|
+
p_run_id uuid,
|
|
235
|
+
p_lease_token uuid,
|
|
236
|
+
p_update jsonb,
|
|
237
|
+
p_now timestamptz
|
|
238
|
+
) RETURNS SETOF public.amalgm_automation_runs
|
|
239
|
+
LANGUAGE plpgsql
|
|
240
|
+
SECURITY DEFINER
|
|
241
|
+
SET search_path = public
|
|
242
|
+
AS $$
|
|
243
|
+
DECLARE
|
|
244
|
+
v_status text := p_update->>'status';
|
|
245
|
+
BEGIN
|
|
246
|
+
IF v_status NOT IN ('pending', 'running', 'completed', 'failed') THEN
|
|
247
|
+
RAISE EXCEPTION 'Invalid run status';
|
|
248
|
+
END IF;
|
|
249
|
+
IF p_update ? 'retryAt' AND v_status <> 'pending' THEN
|
|
250
|
+
RAISE EXCEPTION 'Only a pending run may have retryAt';
|
|
251
|
+
END IF;
|
|
252
|
+
|
|
253
|
+
RETURN QUERY
|
|
254
|
+
UPDATE public.amalgm_automation_runs SET
|
|
255
|
+
status = CASE WHEN v_status = 'pending' AND attempts >= 5 THEN 'failed' ELSE v_status END,
|
|
256
|
+
started_at = CASE WHEN v_status = 'running' THEN COALESCE(started_at, p_now) ELSE started_at END,
|
|
257
|
+
finished_at = CASE
|
|
258
|
+
WHEN v_status IN ('completed', 'failed') OR (v_status = 'pending' AND attempts >= 5) THEN p_now
|
|
259
|
+
WHEN v_status = 'pending' THEN NULL
|
|
260
|
+
ELSE finished_at
|
|
261
|
+
END,
|
|
262
|
+
output = CASE WHEN p_update ? 'output' THEN p_update->'output' ELSE output END,
|
|
263
|
+
error = CASE
|
|
264
|
+
WHEN p_update ? 'error' THEN left(p_update->>'error', 1000)
|
|
265
|
+
WHEN v_status = 'running' THEN NULL
|
|
266
|
+
ELSE error
|
|
267
|
+
END,
|
|
268
|
+
retry_at = CASE
|
|
269
|
+
WHEN v_status = 'pending' AND attempts < 5 AND p_update ? 'retryAt'
|
|
270
|
+
THEN (p_update->>'retryAt')::timestamptz
|
|
271
|
+
ELSE NULL
|
|
272
|
+
END,
|
|
273
|
+
lease_token = CASE WHEN v_status = 'running' THEN lease_token ELSE NULL END,
|
|
274
|
+
lease_expires_at = CASE WHEN v_status = 'running' THEN p_now + interval '60 seconds' ELSE NULL END
|
|
275
|
+
WHERE id = p_run_id AND user_id = p_user_id AND target_id = p_target_id
|
|
276
|
+
AND status IN ('sent', 'running') AND lease_token = p_lease_token
|
|
277
|
+
AND lease_expires_at > p_now
|
|
278
|
+
RETURNING *;
|
|
279
|
+
END;
|
|
280
|
+
$$;
|
|
281
|
+
|
|
282
|
+
CREATE OR REPLACE FUNCTION list_amalgm_runs(
|
|
283
|
+
p_user_id uuid,
|
|
284
|
+
p_automation_id text,
|
|
285
|
+
p_status text,
|
|
286
|
+
p_limit integer,
|
|
287
|
+
p_offset integer
|
|
288
|
+
)
|
|
289
|
+
RETURNS jsonb
|
|
290
|
+
LANGUAGE sql
|
|
291
|
+
SECURITY DEFINER
|
|
292
|
+
SET search_path = public
|
|
293
|
+
AS $$
|
|
294
|
+
WITH filtered AS (
|
|
295
|
+
SELECT id, automation_id, trigger_id, workflow_id, target_id, status, attempts,
|
|
296
|
+
retry_at, input, created_at, sent_at, started_at, finished_at, output, error
|
|
297
|
+
FROM public.amalgm_automation_runs
|
|
298
|
+
WHERE user_id = p_user_id AND automation_id = p_automation_id
|
|
299
|
+
AND (p_status IS NULL OR status = p_status)
|
|
300
|
+
), page_rows AS (
|
|
301
|
+
SELECT * FROM filtered ORDER BY created_at DESC, id DESC LIMIT p_limit OFFSET p_offset
|
|
302
|
+
)
|
|
303
|
+
SELECT jsonb_build_object(
|
|
304
|
+
'items', COALESCE((SELECT jsonb_agg(to_jsonb(page_rows)) FROM page_rows), '[]'::jsonb),
|
|
305
|
+
'total', (SELECT count(*) FROM filtered)
|
|
306
|
+
);
|
|
307
|
+
$$;
|
|
308
|
+
|
|
309
|
+
CREATE OR REPLACE FUNCTION get_amalgm_run(p_user_id uuid, p_automation_id text, p_run_id text)
|
|
310
|
+
RETURNS jsonb
|
|
311
|
+
LANGUAGE sql
|
|
312
|
+
SECURITY DEFINER
|
|
313
|
+
SET search_path = public
|
|
314
|
+
AS $$
|
|
315
|
+
SELECT to_jsonb(r) - 'user_id' - 'automation_payload' - 'lease_token' - 'lease_expires_at'
|
|
316
|
+
FROM public.amalgm_automation_runs r
|
|
317
|
+
WHERE r.user_id = p_user_id AND r.automation_id = p_automation_id AND r.id::text = p_run_id;
|
|
318
|
+
$$;
|
|
319
|
+
|
|
320
|
+
REVOKE ALL ON FUNCTION claim_amalgm_machine_runs(uuid, text, integer, integer) FROM PUBLIC;
|
|
321
|
+
REVOKE ALL ON FUNCTION update_amalgm_claimed_run(uuid, text, uuid, uuid, jsonb, timestamptz) FROM PUBLIC;
|
|
322
|
+
REVOKE ALL ON FUNCTION list_due_amalgm_crons(timestamptz) FROM PUBLIC;
|
|
323
|
+
REVOKE ALL ON FUNCTION list_amalgm_event_triggers(uuid, text) FROM PUBLIC;
|
|
324
|
+
REVOKE ALL ON FUNCTION list_amalgm_runs(uuid, text, text, integer, integer) FROM PUBLIC;
|
|
325
|
+
REVOKE ALL ON FUNCTION get_amalgm_run(uuid, text, text) FROM PUBLIC;
|
|
326
|
+
|
|
327
|
+
GRANT EXECUTE ON FUNCTION claim_amalgm_machine_runs(uuid, text, integer, integer) TO service_role;
|
|
328
|
+
GRANT EXECUTE ON FUNCTION update_amalgm_claimed_run(uuid, text, uuid, uuid, jsonb, timestamptz) TO service_role;
|
|
329
|
+
GRANT EXECUTE ON FUNCTION list_due_amalgm_crons(timestamptz) TO service_role;
|
|
330
|
+
GRANT EXECUTE ON FUNCTION list_amalgm_event_triggers(uuid, text) TO service_role;
|
|
331
|
+
GRANT EXECUTE ON FUNCTION list_amalgm_runs(uuid, text, text, integer, integer) TO service_role;
|
|
332
|
+
GRANT EXECUTE ON FUNCTION get_amalgm_run(uuid, text, text) TO service_role;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
-- One public webhook URL resolves exactly one trigger. The endpoint locator is
|
|
2
|
+
-- not itself a credential: the hosted service adds and verifies its HMAC.
|
|
3
|
+
|
|
4
|
+
ALTER TABLE public.amalgm_automation_triggers
|
|
5
|
+
ADD COLUMN IF NOT EXISTS endpoint_id text;
|
|
6
|
+
|
|
7
|
+
UPDATE public.amalgm_automation_triggers
|
|
8
|
+
SET endpoint_id = encode(gen_random_bytes(16), 'hex')
|
|
9
|
+
WHERE kind = 'webhook' AND endpoint_id IS NULL;
|
|
10
|
+
|
|
11
|
+
CREATE UNIQUE INDEX IF NOT EXISTS amalgm_webhook_endpoint_id
|
|
12
|
+
ON public.amalgm_automation_triggers(endpoint_id)
|
|
13
|
+
WHERE endpoint_id IS NOT NULL;
|
|
14
|
+
|
|
15
|
+
DO $$
|
|
16
|
+
DECLARE
|
|
17
|
+
v_constraint text;
|
|
18
|
+
BEGIN
|
|
19
|
+
SELECT c.conname INTO v_constraint
|
|
20
|
+
FROM pg_constraint c
|
|
21
|
+
WHERE c.conrelid = 'public.amalgm_automation_triggers'::regclass
|
|
22
|
+
AND c.contype = 'c'
|
|
23
|
+
AND pg_get_constraintdef(c.oid) LIKE '%length(secret)%'
|
|
24
|
+
LIMIT 1;
|
|
25
|
+
IF v_constraint IS NOT NULL THEN
|
|
26
|
+
EXECUTE format('ALTER TABLE public.amalgm_automation_triggers DROP CONSTRAINT %I', v_constraint);
|
|
27
|
+
END IF;
|
|
28
|
+
END;
|
|
29
|
+
$$;
|
|
30
|
+
|
|
31
|
+
ALTER TABLE public.amalgm_automation_triggers
|
|
32
|
+
DROP CONSTRAINT IF EXISTS amalgm_automation_trigger_shape;
|
|
33
|
+
ALTER TABLE public.amalgm_automation_triggers
|
|
34
|
+
ADD CONSTRAINT amalgm_automation_trigger_shape CHECK (
|
|
35
|
+
(kind = 'schedule' AND cron IS NOT NULL AND timezone IS NOT NULL
|
|
36
|
+
AND next_run_at IS NOT NULL AND source IS NULL AND event IS NULL
|
|
37
|
+
AND secret IS NULL AND endpoint_id IS NULL)
|
|
38
|
+
OR
|
|
39
|
+
(kind = 'webhook' AND cron IS NULL AND timezone IS NULL
|
|
40
|
+
AND next_run_at IS NULL AND source IS NOT NULL AND event IS NOT NULL
|
|
41
|
+
AND (secret IS NULL OR length(secret) >= 16)
|
|
42
|
+
AND endpoint_id ~ '^[a-f0-9]{32}$')
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
ALTER TABLE public.amalgm_automation_runs
|
|
46
|
+
ADD COLUMN IF NOT EXISTS delivery_key text;
|
|
47
|
+
|
|
48
|
+
ALTER TABLE public.amalgm_automation_runs
|
|
49
|
+
DROP CONSTRAINT IF EXISTS amalgm_automation_run_delivery_key;
|
|
50
|
+
ALTER TABLE public.amalgm_automation_runs
|
|
51
|
+
ADD CONSTRAINT amalgm_automation_run_delivery_key CHECK (
|
|
52
|
+
delivery_key IS NULL OR length(delivery_key) BETWEEN 1 AND 500
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE UNIQUE INDEX IF NOT EXISTS amalgm_automation_webhook_delivery
|
|
56
|
+
ON public.amalgm_automation_runs(user_id, automation_id, trigger_id, delivery_key)
|
|
57
|
+
WHERE delivery_key IS NOT NULL;
|
|
58
|
+
|
|
59
|
+
CREATE OR REPLACE FUNCTION public.amalgm_trigger_json(p_trigger public.amalgm_automation_triggers)
|
|
60
|
+
RETURNS jsonb
|
|
61
|
+
LANGUAGE sql
|
|
62
|
+
IMMUTABLE
|
|
63
|
+
SET search_path = public
|
|
64
|
+
AS $$
|
|
65
|
+
SELECT jsonb_strip_nulls(jsonb_build_object(
|
|
66
|
+
'id', p_trigger.id,
|
|
67
|
+
'automation_id', p_trigger.automation_id,
|
|
68
|
+
'kind', p_trigger.kind,
|
|
69
|
+
'enabled', p_trigger.enabled,
|
|
70
|
+
'cron', p_trigger.cron,
|
|
71
|
+
'timezone', p_trigger.timezone,
|
|
72
|
+
'next_run_at', p_trigger.next_run_at,
|
|
73
|
+
'max_occurrences', p_trigger.max_occurrences,
|
|
74
|
+
'remaining_occurrences', p_trigger.remaining_occurrences,
|
|
75
|
+
'source', p_trigger.source,
|
|
76
|
+
'event', p_trigger.event,
|
|
77
|
+
'endpoint_id', p_trigger.endpoint_id,
|
|
78
|
+
'secret_configured', p_trigger.secret IS NOT NULL,
|
|
79
|
+
'created_at', p_trigger.created_at,
|
|
80
|
+
'updated_at', p_trigger.updated_at
|
|
81
|
+
));
|
|
82
|
+
$$;
|
|
83
|
+
|
|
84
|
+
CREATE OR REPLACE FUNCTION public.create_amalgm_webhook_trigger(
|
|
85
|
+
p_user_id uuid,
|
|
86
|
+
p_automation_id text,
|
|
87
|
+
p_trigger jsonb
|
|
88
|
+
)
|
|
89
|
+
RETURNS jsonb
|
|
90
|
+
LANGUAGE plpgsql
|
|
91
|
+
SECURITY DEFINER
|
|
92
|
+
SET search_path = public
|
|
93
|
+
AS $$
|
|
94
|
+
DECLARE
|
|
95
|
+
v_row public.amalgm_automation_triggers;
|
|
96
|
+
BEGIN
|
|
97
|
+
INSERT INTO public.amalgm_automation_triggers (
|
|
98
|
+
user_id, automation_id, id, kind, enabled, source, event, secret, endpoint_id
|
|
99
|
+
)
|
|
100
|
+
SELECT p_user_id, p_automation_id, p_trigger->>'id', 'webhook',
|
|
101
|
+
(p_trigger->>'enabled')::boolean, p_trigger->>'source', p_trigger->>'event',
|
|
102
|
+
NULLIF(p_trigger->>'secret', ''), p_trigger->>'endpointId'
|
|
103
|
+
WHERE EXISTS (
|
|
104
|
+
SELECT 1 FROM public.amalgm_automations
|
|
105
|
+
WHERE user_id = p_user_id AND id = p_automation_id
|
|
106
|
+
)
|
|
107
|
+
RETURNING * INTO v_row;
|
|
108
|
+
IF NOT FOUND THEN RETURN NULL; END IF;
|
|
109
|
+
RETURN public.amalgm_trigger_json(v_row);
|
|
110
|
+
END;
|
|
111
|
+
$$;
|
|
112
|
+
|
|
113
|
+
CREATE OR REPLACE FUNCTION public.update_amalgm_webhook_trigger(
|
|
114
|
+
p_user_id uuid,
|
|
115
|
+
p_automation_id text,
|
|
116
|
+
p_trigger_id text,
|
|
117
|
+
p_patch jsonb
|
|
118
|
+
)
|
|
119
|
+
RETURNS jsonb
|
|
120
|
+
LANGUAGE plpgsql
|
|
121
|
+
SECURITY DEFINER
|
|
122
|
+
SET search_path = public
|
|
123
|
+
AS $$
|
|
124
|
+
DECLARE
|
|
125
|
+
v_row public.amalgm_automation_triggers;
|
|
126
|
+
BEGIN
|
|
127
|
+
UPDATE public.amalgm_automation_triggers SET
|
|
128
|
+
source = CASE WHEN p_patch ? 'source' THEN p_patch->>'source' ELSE source END,
|
|
129
|
+
event = CASE WHEN p_patch ? 'event' THEN p_patch->>'event' ELSE event END,
|
|
130
|
+
secret = CASE WHEN p_patch ? 'secret' THEN p_patch->>'secret' ELSE secret END,
|
|
131
|
+
endpoint_id = CASE WHEN p_patch ? 'endpointId' THEN p_patch->>'endpointId' ELSE endpoint_id END,
|
|
132
|
+
enabled = CASE WHEN p_patch ? 'enabled' THEN (p_patch->>'enabled')::boolean ELSE enabled END,
|
|
133
|
+
updated_at = now()
|
|
134
|
+
WHERE user_id = p_user_id AND automation_id = p_automation_id
|
|
135
|
+
AND id = p_trigger_id AND kind = 'webhook'
|
|
136
|
+
RETURNING * INTO v_row;
|
|
137
|
+
IF NOT FOUND THEN RETURN NULL; END IF;
|
|
138
|
+
RETURN public.amalgm_trigger_json(v_row);
|
|
139
|
+
END;
|
|
140
|
+
$$;
|
|
141
|
+
|
|
142
|
+
CREATE FUNCTION public.resolve_amalgm_webhook(p_endpoint_id text)
|
|
143
|
+
RETURNS TABLE (
|
|
144
|
+
user_id uuid,
|
|
145
|
+
automation_id text,
|
|
146
|
+
trigger_id text,
|
|
147
|
+
target_id text,
|
|
148
|
+
endpoint_id text,
|
|
149
|
+
source text,
|
|
150
|
+
event text,
|
|
151
|
+
secret text
|
|
152
|
+
)
|
|
153
|
+
LANGUAGE sql
|
|
154
|
+
SECURITY DEFINER
|
|
155
|
+
SET search_path = public
|
|
156
|
+
AS $$
|
|
157
|
+
SELECT t.user_id, t.automation_id, t.id, a.target_id,
|
|
158
|
+
t.endpoint_id, t.source, t.event, t.secret
|
|
159
|
+
FROM public.amalgm_automation_triggers t
|
|
160
|
+
JOIN public.amalgm_automations a
|
|
161
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
162
|
+
JOIN public.amalgm_automation_workflows w
|
|
163
|
+
ON w.user_id = a.user_id AND w.automation_id = a.id
|
|
164
|
+
WHERE t.kind = 'webhook' AND t.endpoint_id = p_endpoint_id
|
|
165
|
+
AND t.enabled AND a.enabled
|
|
166
|
+
AND jsonb_typeof(w.compiled) = 'object'
|
|
167
|
+
AND w.compiled->>'version' = '1'
|
|
168
|
+
AND jsonb_typeof(w.compiled->'steps') = 'array'
|
|
169
|
+
AND jsonb_array_length(
|
|
170
|
+
CASE WHEN jsonb_typeof(w.compiled->'steps') = 'array'
|
|
171
|
+
THEN w.compiled->'steps' ELSE '[]'::jsonb END
|
|
172
|
+
) BETWEEN 1 AND 100;
|
|
173
|
+
$$;
|
|
174
|
+
|
|
175
|
+
CREATE FUNCTION public.enqueue_amalgm_run(
|
|
176
|
+
p_kind text,
|
|
177
|
+
p_user_id uuid,
|
|
178
|
+
p_automation_id text,
|
|
179
|
+
p_trigger_id text,
|
|
180
|
+
p_target_id text,
|
|
181
|
+
p_endpoint_id text,
|
|
182
|
+
p_delivery_key text,
|
|
183
|
+
p_expected_next_run_at timestamptz,
|
|
184
|
+
p_next_run_at timestamptz,
|
|
185
|
+
p_input jsonb,
|
|
186
|
+
p_now timestamptz
|
|
187
|
+
) RETURNS SETOF public.amalgm_automation_runs
|
|
188
|
+
LANGUAGE plpgsql
|
|
189
|
+
SECURITY DEFINER
|
|
190
|
+
SET search_path = public
|
|
191
|
+
AS $$
|
|
192
|
+
DECLARE
|
|
193
|
+
v_inserted integer := 0;
|
|
194
|
+
BEGIN
|
|
195
|
+
IF p_kind = 'cron' THEN
|
|
196
|
+
IF p_endpoint_id IS NOT NULL OR p_delivery_key IS NOT NULL THEN
|
|
197
|
+
RAISE EXCEPTION 'Schedule admission cannot carry webhook identity';
|
|
198
|
+
END IF;
|
|
199
|
+
PERFORM 1
|
|
200
|
+
FROM public.amalgm_automation_triggers t
|
|
201
|
+
JOIN public.amalgm_automations a
|
|
202
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
203
|
+
WHERE t.user_id = p_user_id AND t.automation_id = p_automation_id
|
|
204
|
+
AND t.id = p_trigger_id AND a.target_id = p_target_id
|
|
205
|
+
AND t.kind = 'schedule' AND t.next_run_at = p_expected_next_run_at
|
|
206
|
+
AND (t.remaining_occurrences IS NULL OR t.remaining_occurrences > 0)
|
|
207
|
+
AND t.enabled AND a.enabled
|
|
208
|
+
FOR UPDATE OF t;
|
|
209
|
+
ELSIF p_kind = 'event' THEN
|
|
210
|
+
IF p_endpoint_id IS NULL THEN RAISE EXCEPTION 'Webhook endpoint is required'; END IF;
|
|
211
|
+
PERFORM 1
|
|
212
|
+
FROM public.amalgm_automation_triggers t
|
|
213
|
+
JOIN public.amalgm_automations a
|
|
214
|
+
ON a.user_id = t.user_id AND a.id = t.automation_id
|
|
215
|
+
WHERE t.user_id = p_user_id AND t.automation_id = p_automation_id
|
|
216
|
+
AND t.id = p_trigger_id AND a.target_id = p_target_id
|
|
217
|
+
AND t.kind = 'webhook' AND t.endpoint_id = p_endpoint_id
|
|
218
|
+
AND t.enabled AND a.enabled;
|
|
219
|
+
ELSE
|
|
220
|
+
RAISE EXCEPTION 'Invalid automation trigger kind';
|
|
221
|
+
END IF;
|
|
222
|
+
IF NOT FOUND THEN RETURN; END IF;
|
|
223
|
+
|
|
224
|
+
RETURN QUERY
|
|
225
|
+
INSERT INTO public.amalgm_automation_runs (
|
|
226
|
+
user_id, target_id, automation_id, trigger_id, workflow_id,
|
|
227
|
+
automation_payload, input, status, delivery_key, created_at
|
|
228
|
+
)
|
|
229
|
+
SELECT a.user_id, a.target_id, a.id, t.id, w.id,
|
|
230
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
231
|
+
'id', a.id, 'targetId', a.target_id, 'name', a.name,
|
|
232
|
+
'description', a.description, 'enabled', a.enabled,
|
|
233
|
+
'trigger', jsonb_strip_nulls(jsonb_build_object(
|
|
234
|
+
'id', t.id,
|
|
235
|
+
'kind', CASE WHEN t.kind = 'schedule' THEN 'cron' ELSE 'event' END,
|
|
236
|
+
'enabled', t.enabled, 'cron', t.cron, 'timezone', t.timezone,
|
|
237
|
+
'source', t.source, 'event', t.event
|
|
238
|
+
)),
|
|
239
|
+
'workflow', jsonb_strip_nulls(jsonb_build_object(
|
|
240
|
+
'id', w.id, 'name', w.name, 'script', w.script,
|
|
241
|
+
'compiled', w.compiled, 'allowlist', w.allowlist, 'limits', w.limits
|
|
242
|
+
))
|
|
243
|
+
)),
|
|
244
|
+
p_input, 'pending', p_delivery_key, p_now
|
|
245
|
+
FROM public.amalgm_automations a
|
|
246
|
+
JOIN public.amalgm_automation_triggers t
|
|
247
|
+
ON t.user_id = a.user_id AND t.automation_id = a.id
|
|
248
|
+
JOIN public.amalgm_automation_workflows w
|
|
249
|
+
ON w.user_id = a.user_id AND w.automation_id = a.id
|
|
250
|
+
WHERE a.user_id = p_user_id AND a.id = p_automation_id AND t.id = p_trigger_id
|
|
251
|
+
AND a.target_id = p_target_id AND t.enabled AND a.enabled
|
|
252
|
+
AND (p_kind = 'cron' OR (t.kind = 'webhook' AND t.endpoint_id = p_endpoint_id))
|
|
253
|
+
AND jsonb_typeof(w.compiled) = 'object'
|
|
254
|
+
AND w.compiled->>'version' = '1'
|
|
255
|
+
AND jsonb_typeof(w.compiled->'steps') = 'array'
|
|
256
|
+
AND jsonb_array_length(
|
|
257
|
+
CASE WHEN jsonb_typeof(w.compiled->'steps') = 'array'
|
|
258
|
+
THEN w.compiled->'steps' ELSE '[]'::jsonb END
|
|
259
|
+
) BETWEEN 1 AND 100
|
|
260
|
+
ON CONFLICT (user_id, automation_id, trigger_id, delivery_key)
|
|
261
|
+
WHERE delivery_key IS NOT NULL
|
|
262
|
+
DO UPDATE SET delivery_key = EXCLUDED.delivery_key
|
|
263
|
+
RETURNING *;
|
|
264
|
+
GET DIAGNOSTICS v_inserted = ROW_COUNT;
|
|
265
|
+
|
|
266
|
+
IF p_kind = 'cron' AND v_inserted = 1 THEN
|
|
267
|
+
UPDATE public.amalgm_automation_triggers SET
|
|
268
|
+
next_run_at = p_next_run_at,
|
|
269
|
+
remaining_occurrences = CASE WHEN remaining_occurrences IS NULL
|
|
270
|
+
THEN NULL ELSE remaining_occurrences - 1 END,
|
|
271
|
+
enabled = CASE WHEN remaining_occurrences = 1 THEN false ELSE enabled END,
|
|
272
|
+
updated_at = p_now
|
|
273
|
+
WHERE user_id = p_user_id AND automation_id = p_automation_id
|
|
274
|
+
AND id = p_trigger_id AND kind = 'schedule'
|
|
275
|
+
AND next_run_at = p_expected_next_run_at;
|
|
276
|
+
END IF;
|
|
277
|
+
END;
|
|
278
|
+
$$;
|
|
279
|
+
|
|
280
|
+
REVOKE ALL ON FUNCTION public.resolve_amalgm_webhook(text) FROM PUBLIC;
|
|
281
|
+
REVOKE ALL ON FUNCTION public.enqueue_amalgm_run(
|
|
282
|
+
text, uuid, text, text, text, text, text, timestamptz, timestamptz, jsonb, timestamptz
|
|
283
|
+
) FROM PUBLIC;
|
|
284
|
+
GRANT EXECUTE ON FUNCTION public.resolve_amalgm_webhook(text) TO service_role;
|
|
285
|
+
GRANT EXECUTE ON FUNCTION public.enqueue_amalgm_run(
|
|
286
|
+
text, uuid, text, text, text, text, text, timestamptz, timestamptz, jsonb, timestamptz
|
|
287
|
+
) TO service_role;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
-- Run Now is triggerless admission into the existing durable run ledger. The
|
|
2
|
+
-- selected machine claims and executes the resulting pending run normally.
|
|
3
|
+
|
|
4
|
+
CREATE FUNCTION public.enqueue_amalgm_manual_run(
|
|
5
|
+
p_user_id uuid,
|
|
6
|
+
p_automation_id text,
|
|
7
|
+
p_input jsonb,
|
|
8
|
+
p_idempotency_key text,
|
|
9
|
+
p_now timestamptz
|
|
10
|
+
) RETURNS SETOF public.amalgm_automation_runs
|
|
11
|
+
LANGUAGE plpgsql
|
|
12
|
+
SECURITY DEFINER
|
|
13
|
+
SET search_path = public
|
|
14
|
+
AS $$
|
|
15
|
+
BEGIN
|
|
16
|
+
IF p_idempotency_key IS NOT NULL THEN
|
|
17
|
+
RETURN QUERY
|
|
18
|
+
SELECT r.*
|
|
19
|
+
FROM public.amalgm_automation_runs r
|
|
20
|
+
WHERE r.user_id = p_user_id
|
|
21
|
+
AND r.automation_id = p_automation_id
|
|
22
|
+
AND r.trigger_id = '@manual'
|
|
23
|
+
AND r.delivery_key = p_idempotency_key;
|
|
24
|
+
IF FOUND THEN RETURN; END IF;
|
|
25
|
+
END IF;
|
|
26
|
+
|
|
27
|
+
RETURN QUERY
|
|
28
|
+
INSERT INTO public.amalgm_automation_runs (
|
|
29
|
+
user_id, target_id, automation_id, trigger_id, workflow_id,
|
|
30
|
+
automation_payload, input, status, delivery_key, created_at
|
|
31
|
+
)
|
|
32
|
+
SELECT a.user_id, a.target_id, a.id, '@manual', w.id,
|
|
33
|
+
jsonb_strip_nulls(jsonb_build_object(
|
|
34
|
+
'id', a.id, 'targetId', a.target_id, 'name', a.name,
|
|
35
|
+
'description', a.description, 'enabled', a.enabled,
|
|
36
|
+
'trigger', jsonb_build_object(
|
|
37
|
+
'id', '@manual', 'kind', 'manual', 'enabled', true
|
|
38
|
+
),
|
|
39
|
+
'workflow', jsonb_strip_nulls(jsonb_build_object(
|
|
40
|
+
'id', w.id, 'name', w.name, 'script', w.script,
|
|
41
|
+
'compiled', w.compiled, 'allowlist', w.allowlist, 'limits', w.limits
|
|
42
|
+
))
|
|
43
|
+
)),
|
|
44
|
+
p_input, 'pending', p_idempotency_key, p_now
|
|
45
|
+
FROM public.amalgm_automations a
|
|
46
|
+
JOIN public.amalgm_automation_workflows w
|
|
47
|
+
ON w.user_id = a.user_id AND w.automation_id = a.id
|
|
48
|
+
WHERE a.user_id = p_user_id AND a.id = p_automation_id AND a.enabled
|
|
49
|
+
AND jsonb_typeof(w.compiled) = 'object'
|
|
50
|
+
AND w.compiled->>'version' = '1'
|
|
51
|
+
AND jsonb_typeof(w.compiled->'steps') = 'array'
|
|
52
|
+
AND jsonb_array_length(
|
|
53
|
+
CASE WHEN jsonb_typeof(w.compiled->'steps') = 'array'
|
|
54
|
+
THEN w.compiled->'steps' ELSE '[]'::jsonb END
|
|
55
|
+
) BETWEEN 1 AND 100
|
|
56
|
+
ON CONFLICT (user_id, automation_id, trigger_id, delivery_key)
|
|
57
|
+
WHERE delivery_key IS NOT NULL
|
|
58
|
+
DO UPDATE SET delivery_key = EXCLUDED.delivery_key
|
|
59
|
+
RETURNING *;
|
|
60
|
+
END;
|
|
61
|
+
$$;
|
|
62
|
+
|
|
63
|
+
REVOKE ALL ON FUNCTION public.enqueue_amalgm_manual_run(
|
|
64
|
+
uuid, text, jsonb, text, timestamptz
|
|
65
|
+
) FROM PUBLIC;
|
|
66
|
+
GRANT EXECUTE ON FUNCTION public.enqueue_amalgm_manual_run(
|
|
67
|
+
uuid, text, jsonb, text, timestamptz
|
|
68
|
+
) TO service_role;
|