@team-semicolon/semicolony-cli 4.18.51 → 4.18.52
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/bundle.js +1524 -1046
- package/migrations/149_hive_cron_capability_templates.sql +44 -0
- package/migrations/150_hive_cron_action_contract.sql +38 -0
- package/migrations/151_hive_cron_occurrence_leases.sql +150 -0
- package/migrations/152_hive_cron_relay_outbox.sql +243 -0
- package/migrations/153_hive_cron_evidence_uniqueness.sql +9 -0
- package/migrations/154_hive_cron_evidence_provider_grants.sql +816 -0
- package/migrations/155_hive_cron_occurrence_lease_provider_grants.sql +44 -0
- package/migrations/156_hive_cron_relay_outbox_provider_grants.sql +47 -0
- package/migrations/157_hive_cron_trusted_config_revisions.sql +243 -0
- package/migrations/158_hive_cron_typed_dispatch_outbox.sql +1406 -0
- package/migrations/159_hive_cron_shadow_cursor.sql +600 -0
- package/migrations/160_hive_cron_failure_alert_outbox.sql +406 -0
- package/migrations/161_skill_governance.sql +529 -0
- package/migrations/161_workspace_projection_grants.sql +39 -0
- package/migrations/162_sc_app_kb_history_trigger_hardening.sql +96 -0
- package/package.json +4 -2
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
-- @schema-retarget: off
|
|
2
|
+
-- Hive cron v2 evidence writer: explicit least-privilege sc_provider ACLs.
|
|
3
|
+
-- Migration 135 must have created the non-bypass provider role and RLS helpers.
|
|
4
|
+
-- The migration runner supplies the transaction boundary; do not add one here.
|
|
5
|
+
|
|
6
|
+
CREATE OR REPLACE FUNCTION semicolony.hive_cron_get_template(p_job_id text)
|
|
7
|
+
RETURNS TABLE (
|
|
8
|
+
bot_id text,
|
|
9
|
+
job_id text,
|
|
10
|
+
runtime_source text,
|
|
11
|
+
permission text,
|
|
12
|
+
resource_scopes text[],
|
|
13
|
+
destination text,
|
|
14
|
+
timeout_seconds integer,
|
|
15
|
+
idempotency_key_template text,
|
|
16
|
+
expected_outcome_digest text,
|
|
17
|
+
source_hash text,
|
|
18
|
+
action_kind text,
|
|
19
|
+
action_version integer,
|
|
20
|
+
action_contract_digest text,
|
|
21
|
+
updated_at timestamptz
|
|
22
|
+
)
|
|
23
|
+
LANGUAGE sql
|
|
24
|
+
SECURITY DEFINER
|
|
25
|
+
SET search_path = pg_catalog
|
|
26
|
+
AS $$
|
|
27
|
+
SELECT template.bot_id,
|
|
28
|
+
template.job_id,
|
|
29
|
+
template.runtime_source,
|
|
30
|
+
template.permission,
|
|
31
|
+
template.resource_scopes,
|
|
32
|
+
template.destination,
|
|
33
|
+
template.timeout_seconds,
|
|
34
|
+
template.idempotency_key_template,
|
|
35
|
+
template.expected_outcome_digest,
|
|
36
|
+
template.source_hash,
|
|
37
|
+
template.action_kind,
|
|
38
|
+
template.action_version,
|
|
39
|
+
template.action_contract_digest,
|
|
40
|
+
template.updated_at
|
|
41
|
+
FROM semicolony.cron_capability_templates AS template
|
|
42
|
+
WHERE template.bot_id = 'semi'
|
|
43
|
+
AND template.job_id = p_job_id
|
|
44
|
+
AND template.runtime_source = 'hive-cron-v2';
|
|
45
|
+
$$;
|
|
46
|
+
|
|
47
|
+
CREATE OR REPLACE FUNCTION semicolony.hive_cron_lock_authority(
|
|
48
|
+
p_job_id text,
|
|
49
|
+
p_idempotency_key text,
|
|
50
|
+
p_claim_id text
|
|
51
|
+
)
|
|
52
|
+
RETURNS TABLE (authority_status text)
|
|
53
|
+
LANGUAGE plpgsql
|
|
54
|
+
SECURITY DEFINER
|
|
55
|
+
SET search_path = pg_catalog
|
|
56
|
+
AS $$
|
|
57
|
+
DECLARE
|
|
58
|
+
job_runtime_source text;
|
|
59
|
+
lease_state text;
|
|
60
|
+
BEGIN
|
|
61
|
+
IF p_job_id IS NULL
|
|
62
|
+
OR p_job_id !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
|
63
|
+
OR p_idempotency_key IS NULL
|
|
64
|
+
OR p_idempotency_key !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$'
|
|
65
|
+
OR pg_catalog.char_length(p_idempotency_key)
|
|
66
|
+
IS DISTINCT FROM pg_catalog.char_length('cron:' || p_job_id || ':') + 24
|
|
67
|
+
OR pg_catalog.left(
|
|
68
|
+
p_idempotency_key,
|
|
69
|
+
pg_catalog.char_length('cron:' || p_job_id || ':')
|
|
70
|
+
) IS DISTINCT FROM 'cron:' || p_job_id || ':'
|
|
71
|
+
OR pg_catalog.right(p_idempotency_key, 24)
|
|
72
|
+
!~ '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
73
|
+
OR p_claim_id IS NULL
|
|
74
|
+
OR p_claim_id !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$' THEN
|
|
75
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text;
|
|
76
|
+
RETURN;
|
|
77
|
+
END IF;
|
|
78
|
+
|
|
79
|
+
SELECT job.runtime_source
|
|
80
|
+
INTO job_runtime_source
|
|
81
|
+
FROM semicolony.bot_cron_jobs AS job
|
|
82
|
+
WHERE job.bot_id = 'semi'
|
|
83
|
+
AND job.job_id = p_job_id
|
|
84
|
+
FOR UPDATE;
|
|
85
|
+
|
|
86
|
+
IF NOT FOUND THEN
|
|
87
|
+
RETURN QUERY SELECT 'evidence_job_not_found'::text;
|
|
88
|
+
RETURN;
|
|
89
|
+
END IF;
|
|
90
|
+
IF job_runtime_source IS DISTINCT FROM 'hive-cron-v2' THEN
|
|
91
|
+
RETURN QUERY SELECT 'evidence_job_runtime_mismatch'::text;
|
|
92
|
+
RETURN;
|
|
93
|
+
END IF;
|
|
94
|
+
|
|
95
|
+
PERFORM template.job_id
|
|
96
|
+
FROM semicolony.cron_capability_templates AS template
|
|
97
|
+
WHERE template.bot_id = 'semi'
|
|
98
|
+
AND template.job_id = p_job_id
|
|
99
|
+
AND template.runtime_source = 'hive-cron-v2'
|
|
100
|
+
AND template.action_kind IS NOT NULL
|
|
101
|
+
AND template.action_version IS NOT NULL
|
|
102
|
+
AND template.action_contract_digest IS NOT NULL
|
|
103
|
+
FOR UPDATE;
|
|
104
|
+
|
|
105
|
+
IF NOT FOUND THEN
|
|
106
|
+
RETURN QUERY SELECT 'current_template_not_found'::text;
|
|
107
|
+
RETURN;
|
|
108
|
+
END IF;
|
|
109
|
+
|
|
110
|
+
SELECT lease.state
|
|
111
|
+
INTO lease_state
|
|
112
|
+
FROM semicolony.cron_read_action_occurrence_leases AS lease
|
|
113
|
+
WHERE lease.idempotency_key = p_idempotency_key
|
|
114
|
+
AND lease.claim_id = p_claim_id
|
|
115
|
+
FOR UPDATE;
|
|
116
|
+
|
|
117
|
+
IF NOT FOUND THEN
|
|
118
|
+
RETURN QUERY SELECT 'occurrence_lease_not_owned'::text;
|
|
119
|
+
RETURN;
|
|
120
|
+
END IF;
|
|
121
|
+
IF lease_state IS NULL OR lease_state NOT IN ('acquired', 'completed') THEN
|
|
122
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text;
|
|
123
|
+
RETURN;
|
|
124
|
+
END IF;
|
|
125
|
+
|
|
126
|
+
PERFORM outbox.idempotency_key
|
|
127
|
+
FROM semicolony.cron_read_action_relay_outbox AS outbox
|
|
128
|
+
WHERE outbox.idempotency_key = p_idempotency_key
|
|
129
|
+
FOR UPDATE;
|
|
130
|
+
|
|
131
|
+
RETURN QUERY SELECT 'locked'::text;
|
|
132
|
+
END;
|
|
133
|
+
$$;
|
|
134
|
+
|
|
135
|
+
CREATE OR REPLACE FUNCTION semicolony.hive_cron_reconcile(
|
|
136
|
+
p_job_id text,
|
|
137
|
+
p_idempotency_key text
|
|
138
|
+
)
|
|
139
|
+
RETURNS TABLE (
|
|
140
|
+
lease_count integer,
|
|
141
|
+
lease_claim_id text,
|
|
142
|
+
lease_state text,
|
|
143
|
+
lease_completed_at timestamptz,
|
|
144
|
+
outbox_count integer,
|
|
145
|
+
outbox_request_digest text,
|
|
146
|
+
outbox_state text,
|
|
147
|
+
outbox_outcome_status text,
|
|
148
|
+
outbox_error_code text,
|
|
149
|
+
outbox_completed_at timestamptz,
|
|
150
|
+
evidence_count integer,
|
|
151
|
+
evidence_commitment_id text,
|
|
152
|
+
evidence_claim_id text,
|
|
153
|
+
evidence_stage text,
|
|
154
|
+
evidence_run_status text,
|
|
155
|
+
evidence_relay_required jsonb,
|
|
156
|
+
evidence_relay_outcome text,
|
|
157
|
+
evidence_relay_request_digest text,
|
|
158
|
+
evidence_output_digest text,
|
|
159
|
+
evidence_completed_at timestamptz,
|
|
160
|
+
event_count integer,
|
|
161
|
+
event_commitment_id text,
|
|
162
|
+
event_type text,
|
|
163
|
+
event_run_status text,
|
|
164
|
+
event_output_digest text,
|
|
165
|
+
event_occurred_at timestamptz
|
|
166
|
+
)
|
|
167
|
+
LANGUAGE sql
|
|
168
|
+
SECURITY DEFINER
|
|
169
|
+
SET search_path = pg_catalog
|
|
170
|
+
AS $$
|
|
171
|
+
WITH authority AS (
|
|
172
|
+
SELECT job.job_id
|
|
173
|
+
FROM semicolony.bot_cron_jobs AS job
|
|
174
|
+
WHERE job.bot_id = 'semi'
|
|
175
|
+
AND job.job_id = p_job_id
|
|
176
|
+
AND job.runtime_source = 'hive-cron-v2'
|
|
177
|
+
AND p_idempotency_key IS NOT NULL
|
|
178
|
+
AND pg_catalog.char_length(p_idempotency_key)
|
|
179
|
+
= pg_catalog.char_length('cron:' || p_job_id || ':') + 24
|
|
180
|
+
AND pg_catalog.left(
|
|
181
|
+
p_idempotency_key,
|
|
182
|
+
pg_catalog.char_length('cron:' || p_job_id || ':')
|
|
183
|
+
) = 'cron:' || p_job_id || ':'
|
|
184
|
+
AND pg_catalog.right(p_idempotency_key, 24)
|
|
185
|
+
~ '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
186
|
+
),
|
|
187
|
+
lease_rows AS (
|
|
188
|
+
SELECT lease.claim_id, lease.state, lease.completed_at
|
|
189
|
+
FROM semicolony.cron_read_action_occurrence_leases AS lease
|
|
190
|
+
WHERE lease.idempotency_key = p_idempotency_key
|
|
191
|
+
AND EXISTS (SELECT 1 FROM authority)
|
|
192
|
+
LIMIT 2
|
|
193
|
+
),
|
|
194
|
+
outbox_rows AS (
|
|
195
|
+
SELECT outbox.request_digest, outbox.state, outbox.outcome_status,
|
|
196
|
+
outbox.error_code, outbox.completed_at
|
|
197
|
+
FROM semicolony.cron_read_action_relay_outbox AS outbox
|
|
198
|
+
WHERE outbox.idempotency_key = p_idempotency_key
|
|
199
|
+
AND EXISTS (SELECT 1 FROM authority)
|
|
200
|
+
LIMIT 2
|
|
201
|
+
),
|
|
202
|
+
evidence_rows AS (
|
|
203
|
+
SELECT evidence.id AS commitment_id,
|
|
204
|
+
evidence.metadata->>'claim_id' AS claim_id,
|
|
205
|
+
evidence.metadata->>'cron_parity_stage' AS stage,
|
|
206
|
+
evidence.metadata->>'run_status' AS run_status,
|
|
207
|
+
evidence.metadata->'relay_required' AS relay_required,
|
|
208
|
+
evidence.metadata->>'relay_outcome' AS relay_outcome,
|
|
209
|
+
evidence.metadata->>'relay_request_digest' AS relay_request_digest,
|
|
210
|
+
evidence.metadata->>'output_digest' AS output_digest,
|
|
211
|
+
evidence.completed_at
|
|
212
|
+
FROM semicolony.bot_commitments AS evidence
|
|
213
|
+
WHERE evidence.bot_id = 'semi'
|
|
214
|
+
AND evidence.source_type = 'cron'
|
|
215
|
+
AND evidence.runtime_source = 'hive-cron-v2'
|
|
216
|
+
AND evidence.metadata->>'cron_parity_runtime' = 'hive-cron-v2'
|
|
217
|
+
AND evidence.metadata->>'idempotency_key' = p_idempotency_key
|
|
218
|
+
AND EXISTS (SELECT 1 FROM authority)
|
|
219
|
+
LIMIT 2
|
|
220
|
+
),
|
|
221
|
+
event_rows AS (
|
|
222
|
+
SELECT event.commitment_id,
|
|
223
|
+
event.event_type,
|
|
224
|
+
event.payload->>'run_status' AS run_status,
|
|
225
|
+
event.payload->>'output_digest' AS output_digest,
|
|
226
|
+
event.occurred_at
|
|
227
|
+
FROM semicolony.commitment_events AS event
|
|
228
|
+
WHERE event.bot_id = 'semi'
|
|
229
|
+
AND event.source_type = 'cron'
|
|
230
|
+
AND event.runtime_source = 'hive-cron-v2'
|
|
231
|
+
AND event.event_type = 'cron_run_recorded'
|
|
232
|
+
AND event.payload->>'job_id' = p_job_id
|
|
233
|
+
AND event.payload->>'occurrence_idempotency_key' = p_idempotency_key
|
|
234
|
+
AND event.idempotency_key = 'cron:' || event.commitment_id
|
|
235
|
+
AND EXISTS (SELECT 1 FROM authority)
|
|
236
|
+
LIMIT 2
|
|
237
|
+
)
|
|
238
|
+
SELECT
|
|
239
|
+
(SELECT COUNT(*)::integer FROM lease_rows),
|
|
240
|
+
(SELECT claim_id FROM lease_rows LIMIT 1),
|
|
241
|
+
(SELECT state FROM lease_rows LIMIT 1),
|
|
242
|
+
(SELECT completed_at FROM lease_rows LIMIT 1),
|
|
243
|
+
(SELECT COUNT(*)::integer FROM outbox_rows),
|
|
244
|
+
(SELECT request_digest FROM outbox_rows LIMIT 1),
|
|
245
|
+
(SELECT state FROM outbox_rows LIMIT 1),
|
|
246
|
+
(SELECT outcome_status FROM outbox_rows LIMIT 1),
|
|
247
|
+
(SELECT error_code FROM outbox_rows LIMIT 1),
|
|
248
|
+
(SELECT completed_at FROM outbox_rows LIMIT 1),
|
|
249
|
+
(SELECT COUNT(*)::integer FROM evidence_rows),
|
|
250
|
+
(SELECT commitment_id FROM evidence_rows LIMIT 1),
|
|
251
|
+
(SELECT claim_id FROM evidence_rows LIMIT 1),
|
|
252
|
+
(SELECT stage FROM evidence_rows LIMIT 1),
|
|
253
|
+
(SELECT run_status FROM evidence_rows LIMIT 1),
|
|
254
|
+
(SELECT relay_required FROM evidence_rows LIMIT 1),
|
|
255
|
+
(SELECT relay_outcome FROM evidence_rows LIMIT 1),
|
|
256
|
+
(SELECT relay_request_digest FROM evidence_rows LIMIT 1),
|
|
257
|
+
(SELECT output_digest FROM evidence_rows LIMIT 1),
|
|
258
|
+
(SELECT completed_at FROM evidence_rows LIMIT 1),
|
|
259
|
+
(SELECT COUNT(*)::integer FROM event_rows),
|
|
260
|
+
(SELECT commitment_id FROM event_rows LIMIT 1),
|
|
261
|
+
(SELECT event_type FROM event_rows LIMIT 1),
|
|
262
|
+
(SELECT run_status FROM event_rows LIMIT 1),
|
|
263
|
+
(SELECT output_digest FROM event_rows LIMIT 1),
|
|
264
|
+
(SELECT occurred_at FROM event_rows LIMIT 1)
|
|
265
|
+
WHERE EXISTS (SELECT 1 FROM authority);
|
|
266
|
+
$$;
|
|
267
|
+
|
|
268
|
+
CREATE OR REPLACE FUNCTION semicolony.hive_cron_write_evidence(
|
|
269
|
+
p_job_id text,
|
|
270
|
+
p_idempotency_key text,
|
|
271
|
+
p_claim_id text,
|
|
272
|
+
p_evidence jsonb
|
|
273
|
+
)
|
|
274
|
+
RETURNS TABLE (
|
|
275
|
+
write_status text,
|
|
276
|
+
commitment_id text
|
|
277
|
+
)
|
|
278
|
+
LANGUAGE plpgsql
|
|
279
|
+
SECURITY DEFINER
|
|
280
|
+
SET search_path = pg_catalog
|
|
281
|
+
AS $$
|
|
282
|
+
DECLARE
|
|
283
|
+
envelope_shape_ok boolean;
|
|
284
|
+
metadata_shape_ok boolean;
|
|
285
|
+
metadata_value jsonb;
|
|
286
|
+
stage_value text;
|
|
287
|
+
status_value text;
|
|
288
|
+
started_at_text text;
|
|
289
|
+
started_at_value timestamptz;
|
|
290
|
+
completed_at_value timestamptz;
|
|
291
|
+
duration_ms_value integer;
|
|
292
|
+
output_digest_value text;
|
|
293
|
+
p_commitment_id text;
|
|
294
|
+
job_row record;
|
|
295
|
+
template_row record;
|
|
296
|
+
lease_row record;
|
|
297
|
+
outbox_row record;
|
|
298
|
+
commitment_row record;
|
|
299
|
+
event_row record;
|
|
300
|
+
outbox_exists boolean;
|
|
301
|
+
commitment_exists boolean;
|
|
302
|
+
event_exists boolean;
|
|
303
|
+
expected_pipeline_context jsonb;
|
|
304
|
+
expected_commitment_metadata jsonb;
|
|
305
|
+
expected_event_payload jsonb;
|
|
306
|
+
expected_title text;
|
|
307
|
+
expected_description text;
|
|
308
|
+
affected_count integer;
|
|
309
|
+
BEGIN
|
|
310
|
+
IF p_job_id IS NULL
|
|
311
|
+
OR p_job_id !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
|
312
|
+
OR p_idempotency_key IS NULL
|
|
313
|
+
OR p_idempotency_key !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$'
|
|
314
|
+
OR pg_catalog.char_length(p_idempotency_key)
|
|
315
|
+
IS DISTINCT FROM pg_catalog.char_length('cron:' || p_job_id || ':') + 24
|
|
316
|
+
OR pg_catalog.left(
|
|
317
|
+
p_idempotency_key,
|
|
318
|
+
pg_catalog.char_length('cron:' || p_job_id || ':')
|
|
319
|
+
) IS DISTINCT FROM 'cron:' || p_job_id || ':'
|
|
320
|
+
OR pg_catalog.right(p_idempotency_key, 24)
|
|
321
|
+
!~ '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
322
|
+
OR p_claim_id IS NULL
|
|
323
|
+
OR p_claim_id !~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
|
324
|
+
OR p_evidence IS NULL
|
|
325
|
+
OR pg_catalog.jsonb_typeof(p_evidence) IS DISTINCT FROM 'object'
|
|
326
|
+
OR pg_catalog.octet_length(p_evidence::text) > 65536 THEN
|
|
327
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
328
|
+
RETURN;
|
|
329
|
+
END IF;
|
|
330
|
+
|
|
331
|
+
SELECT pg_catalog.count(*) = 6
|
|
332
|
+
AND pg_catalog.bool_and(
|
|
333
|
+
envelope_key = ANY (ARRAY[
|
|
334
|
+
'commitment_id', 'status', 'started_at', 'duration_ms',
|
|
335
|
+
'output_digest', 'metadata'
|
|
336
|
+
]::text[])
|
|
337
|
+
)
|
|
338
|
+
INTO envelope_shape_ok
|
|
339
|
+
FROM pg_catalog.jsonb_object_keys(p_evidence) AS envelope_keys(envelope_key);
|
|
340
|
+
|
|
341
|
+
IF envelope_shape_ok IS NOT TRUE
|
|
342
|
+
OR pg_catalog.jsonb_typeof(p_evidence->'commitment_id') IS DISTINCT FROM 'string'
|
|
343
|
+
OR pg_catalog.jsonb_typeof(p_evidence->'status') IS DISTINCT FROM 'string'
|
|
344
|
+
OR pg_catalog.jsonb_typeof(p_evidence->'started_at') IS DISTINCT FROM 'string'
|
|
345
|
+
OR pg_catalog.jsonb_typeof(p_evidence->'duration_ms') IS DISTINCT FROM 'number'
|
|
346
|
+
OR pg_catalog.jsonb_typeof(p_evidence->'output_digest') IS DISTINCT FROM 'string'
|
|
347
|
+
OR pg_catalog.jsonb_typeof(p_evidence->'metadata') IS DISTINCT FROM 'object' THEN
|
|
348
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
349
|
+
RETURN;
|
|
350
|
+
END IF;
|
|
351
|
+
|
|
352
|
+
p_commitment_id := p_evidence->>'commitment_id';
|
|
353
|
+
status_value := p_evidence->>'status';
|
|
354
|
+
started_at_text := p_evidence->>'started_at';
|
|
355
|
+
output_digest_value := p_evidence->>'output_digest';
|
|
356
|
+
metadata_value := p_evidence->'metadata';
|
|
357
|
+
|
|
358
|
+
IF p_commitment_id IS NULL
|
|
359
|
+
OR p_commitment_id !~ '^cmt-hive-cron-v2-[a-f0-9]{64}$'
|
|
360
|
+
OR status_value IS NULL
|
|
361
|
+
OR status_value NOT IN ('success', 'skipped')
|
|
362
|
+
OR started_at_text IS NULL
|
|
363
|
+
OR started_at_text !~ '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$'
|
|
364
|
+
OR (p_evidence->>'duration_ms') IS NULL
|
|
365
|
+
OR (p_evidence->>'duration_ms') !~ '^(0|[1-9][0-9]{0,7})$'
|
|
366
|
+
OR (p_evidence->>'duration_ms')::bigint > 86400000
|
|
367
|
+
OR output_digest_value IS NULL
|
|
368
|
+
OR output_digest_value !~ '^sha256:[a-f0-9]{64}$' THEN
|
|
369
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
370
|
+
RETURN;
|
|
371
|
+
END IF;
|
|
372
|
+
|
|
373
|
+
BEGIN
|
|
374
|
+
started_at_value := started_at_text::timestamptz;
|
|
375
|
+
EXCEPTION WHEN OTHERS THEN
|
|
376
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
377
|
+
RETURN;
|
|
378
|
+
END;
|
|
379
|
+
IF pg_catalog.to_char(
|
|
380
|
+
started_at_value AT TIME ZONE 'UTC',
|
|
381
|
+
'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
|
|
382
|
+
) IS DISTINCT FROM started_at_text THEN
|
|
383
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
384
|
+
RETURN;
|
|
385
|
+
END IF;
|
|
386
|
+
|
|
387
|
+
duration_ms_value := (p_evidence->>'duration_ms')::integer;
|
|
388
|
+
completed_at_value := started_at_value + duration_ms_value * '1 millisecond'::interval;
|
|
389
|
+
stage_value := metadata_value->>'cron_parity_stage';
|
|
390
|
+
|
|
391
|
+
SELECT pg_catalog.count(*) = CASE WHEN stage_value = 'shadow' THEN 28 ELSE 24 END
|
|
392
|
+
AND pg_catalog.bool_and(
|
|
393
|
+
metadata_key = ANY (
|
|
394
|
+
CASE WHEN stage_value = 'shadow' THEN ARRAY[
|
|
395
|
+
'cron_parity_runtime', 'cron_parity_stage', 'source_hash',
|
|
396
|
+
'expected_outcome_digest', 'scheduled_at', 'idempotency_key',
|
|
397
|
+
'occurrence_origin', 'enabled_at_claim', 'claim_id', 'lease_acquired',
|
|
398
|
+
'relay_required', 'relay_outcome', 'relay_request_digest', 'facts_digest',
|
|
399
|
+
'duration_ms', 'action_kind', 'action_version', 'action_contract_digest',
|
|
400
|
+
'resource_scopes', 'destination', 'timeout_seconds',
|
|
401
|
+
'idempotency_key_template', 'template_updated_at', 'run_status',
|
|
402
|
+
'shadow_cursor', 'enqueued', 'delivered', 'side_effects'
|
|
403
|
+
]::text[] ELSE ARRAY[
|
|
404
|
+
'cron_parity_runtime', 'cron_parity_stage', 'source_hash',
|
|
405
|
+
'expected_outcome_digest', 'scheduled_at', 'idempotency_key',
|
|
406
|
+
'occurrence_origin', 'enabled_at_claim', 'claim_id', 'lease_acquired',
|
|
407
|
+
'relay_required', 'relay_outcome', 'relay_request_digest', 'facts_digest',
|
|
408
|
+
'duration_ms', 'action_kind', 'action_version', 'action_contract_digest',
|
|
409
|
+
'resource_scopes', 'destination', 'timeout_seconds',
|
|
410
|
+
'idempotency_key_template', 'template_updated_at', 'run_status'
|
|
411
|
+
]::text[] END
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
INTO metadata_shape_ok
|
|
415
|
+
FROM pg_catalog.jsonb_object_keys(metadata_value) AS metadata_keys(metadata_key);
|
|
416
|
+
|
|
417
|
+
IF metadata_shape_ok IS NOT TRUE
|
|
418
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'cron_parity_runtime') IS DISTINCT FROM 'string'
|
|
419
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'cron_parity_stage') IS DISTINCT FROM 'string'
|
|
420
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'source_hash') IS DISTINCT FROM 'string'
|
|
421
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'expected_outcome_digest') IS DISTINCT FROM 'string'
|
|
422
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'scheduled_at') IS DISTINCT FROM 'string'
|
|
423
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'idempotency_key') IS DISTINCT FROM 'string'
|
|
424
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'occurrence_origin') IS DISTINCT FROM 'string'
|
|
425
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'enabled_at_claim') IS DISTINCT FROM 'boolean'
|
|
426
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'claim_id') IS DISTINCT FROM 'string'
|
|
427
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'lease_acquired') IS DISTINCT FROM 'boolean'
|
|
428
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'relay_required') IS DISTINCT FROM 'boolean'
|
|
429
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'relay_outcome') IS DISTINCT FROM 'string'
|
|
430
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'facts_digest') IS DISTINCT FROM 'string'
|
|
431
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'duration_ms') IS DISTINCT FROM 'number'
|
|
432
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'action_kind') IS DISTINCT FROM 'string'
|
|
433
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'action_version') IS DISTINCT FROM 'number'
|
|
434
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'action_contract_digest') IS DISTINCT FROM 'string'
|
|
435
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'resource_scopes') IS DISTINCT FROM 'array'
|
|
436
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'destination') IS DISTINCT FROM 'string'
|
|
437
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'timeout_seconds') IS DISTINCT FROM 'number'
|
|
438
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'idempotency_key_template') IS DISTINCT FROM 'string'
|
|
439
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'template_updated_at') IS DISTINCT FROM 'string'
|
|
440
|
+
OR pg_catalog.jsonb_typeof(metadata_value->'run_status') IS DISTINCT FROM 'string'
|
|
441
|
+
OR metadata_value->>'cron_parity_runtime' IS DISTINCT FROM 'hive-cron-v2'
|
|
442
|
+
OR stage_value IS NULL
|
|
443
|
+
OR stage_value NOT IN ('manual', 'shadow', 'scheduled_primary')
|
|
444
|
+
OR metadata_value->>'scheduled_at' IS DISTINCT FROM started_at_text
|
|
445
|
+
OR metadata_value->>'idempotency_key' IS DISTINCT FROM p_idempotency_key
|
|
446
|
+
OR metadata_value->>'claim_id' IS DISTINCT FROM p_claim_id
|
|
447
|
+
OR metadata_value->'lease_acquired' IS DISTINCT FROM 'true'::jsonb
|
|
448
|
+
OR metadata_value->>'facts_digest' IS DISTINCT FROM output_digest_value
|
|
449
|
+
OR metadata_value->'duration_ms' IS DISTINCT FROM p_evidence->'duration_ms'
|
|
450
|
+
OR metadata_value->>'run_status' IS DISTINCT FROM status_value
|
|
451
|
+
OR metadata_value->>'idempotency_key_template'
|
|
452
|
+
IS DISTINCT FROM 'cron:' || p_job_id || ':{scheduled_for}'
|
|
453
|
+
OR metadata_value->>'template_updated_at' IS NULL
|
|
454
|
+
OR metadata_value->>'template_updated_at'
|
|
455
|
+
!~ '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$' THEN
|
|
456
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
457
|
+
RETURN;
|
|
458
|
+
END IF;
|
|
459
|
+
|
|
460
|
+
IF stage_value = 'manual' AND NOT (
|
|
461
|
+
metadata_value->>'occurrence_origin' IS NOT DISTINCT FROM 'manual'
|
|
462
|
+
AND metadata_value->'enabled_at_claim' IS NOT DISTINCT FROM 'false'::jsonb
|
|
463
|
+
AND status_value = 'success'
|
|
464
|
+
) THEN
|
|
465
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
466
|
+
RETURN;
|
|
467
|
+
ELSIF stage_value = 'scheduled_primary' AND NOT (
|
|
468
|
+
metadata_value->>'occurrence_origin' IS NOT DISTINCT FROM 'scheduled'
|
|
469
|
+
AND metadata_value->'enabled_at_claim' IS NOT DISTINCT FROM 'true'::jsonb
|
|
470
|
+
AND status_value = 'success'
|
|
471
|
+
) THEN
|
|
472
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
473
|
+
RETURN;
|
|
474
|
+
ELSIF stage_value = 'shadow' AND NOT (
|
|
475
|
+
pg_catalog.jsonb_typeof(metadata_value->'shadow_cursor') IS NOT DISTINCT FROM 'string'
|
|
476
|
+
AND pg_catalog.jsonb_typeof(metadata_value->'enqueued') IS NOT DISTINCT FROM 'boolean'
|
|
477
|
+
AND pg_catalog.jsonb_typeof(metadata_value->'delivered') IS NOT DISTINCT FROM 'boolean'
|
|
478
|
+
AND pg_catalog.jsonb_typeof(metadata_value->'side_effects') IS NOT DISTINCT FROM 'boolean'
|
|
479
|
+
AND metadata_value->>'occurrence_origin' IS NOT DISTINCT FROM 'shadow'
|
|
480
|
+
AND metadata_value->'enabled_at_claim' IS NOT DISTINCT FROM 'false'::jsonb
|
|
481
|
+
AND status_value = 'skipped'
|
|
482
|
+
AND metadata_value->'relay_required' IS NOT DISTINCT FROM 'false'::jsonb
|
|
483
|
+
AND metadata_value->>'shadow_cursor' ~ '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
|
|
484
|
+
AND metadata_value->'enqueued' IS NOT DISTINCT FROM 'false'::jsonb
|
|
485
|
+
AND metadata_value->'delivered' IS NOT DISTINCT FROM 'false'::jsonb
|
|
486
|
+
AND metadata_value->'side_effects' IS NOT DISTINCT FROM 'false'::jsonb
|
|
487
|
+
) THEN
|
|
488
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
489
|
+
RETURN;
|
|
490
|
+
END IF;
|
|
491
|
+
|
|
492
|
+
IF metadata_value->'relay_required' IS NOT DISTINCT FROM 'true'::jsonb THEN
|
|
493
|
+
IF pg_catalog.jsonb_typeof(metadata_value->'relay_request_digest') IS DISTINCT FROM 'string'
|
|
494
|
+
OR metadata_value->>'relay_outcome' IS DISTINCT FROM 'delivered'
|
|
495
|
+
OR metadata_value->>'relay_request_digest' !~ '^[a-f0-9]{64}$' THEN
|
|
496
|
+
RETURN QUERY SELECT 'relay_evidence_not_terminal'::text, NULL::text;
|
|
497
|
+
RETURN;
|
|
498
|
+
END IF;
|
|
499
|
+
ELSIF metadata_value->'relay_required' IS NOT DISTINCT FROM 'false'::jsonb THEN
|
|
500
|
+
IF pg_catalog.jsonb_typeof(metadata_value->'relay_request_digest') IS DISTINCT FROM 'null'
|
|
501
|
+
OR metadata_value->>'relay_outcome' IS DISTINCT FROM 'suppressed'
|
|
502
|
+
OR metadata_value->'relay_request_digest' IS DISTINCT FROM 'null'::jsonb THEN
|
|
503
|
+
RETURN QUERY SELECT 'relay_evidence_not_terminal'::text, NULL::text;
|
|
504
|
+
RETURN;
|
|
505
|
+
END IF;
|
|
506
|
+
ELSE
|
|
507
|
+
RETURN QUERY SELECT 'relay_evidence_not_terminal'::text, NULL::text;
|
|
508
|
+
RETURN;
|
|
509
|
+
END IF;
|
|
510
|
+
|
|
511
|
+
SELECT job.name, job.schedule::text AS schedule_expr, job.runtime_source
|
|
512
|
+
INTO job_row
|
|
513
|
+
FROM semicolony.bot_cron_jobs AS job
|
|
514
|
+
WHERE job.bot_id = 'semi'
|
|
515
|
+
AND job.job_id = p_job_id
|
|
516
|
+
FOR UPDATE;
|
|
517
|
+
|
|
518
|
+
IF NOT FOUND THEN
|
|
519
|
+
RETURN QUERY SELECT 'evidence_job_not_found'::text, NULL::text;
|
|
520
|
+
RETURN;
|
|
521
|
+
END IF;
|
|
522
|
+
IF job_row.runtime_source IS DISTINCT FROM 'hive-cron-v2' THEN
|
|
523
|
+
RETURN QUERY SELECT 'evidence_job_runtime_mismatch'::text, NULL::text;
|
|
524
|
+
RETURN;
|
|
525
|
+
END IF;
|
|
526
|
+
IF job_row.name IS NULL OR job_row.schedule_expr IS NULL THEN
|
|
527
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
528
|
+
RETURN;
|
|
529
|
+
END IF;
|
|
530
|
+
|
|
531
|
+
SELECT template.*
|
|
532
|
+
INTO template_row
|
|
533
|
+
FROM semicolony.cron_capability_templates AS template
|
|
534
|
+
WHERE template.bot_id = 'semi'
|
|
535
|
+
AND template.job_id = p_job_id
|
|
536
|
+
AND template.runtime_source = 'hive-cron-v2'
|
|
537
|
+
AND template.action_kind IS NOT NULL
|
|
538
|
+
AND template.action_version IS NOT NULL
|
|
539
|
+
AND template.action_contract_digest IS NOT NULL
|
|
540
|
+
FOR UPDATE;
|
|
541
|
+
|
|
542
|
+
IF NOT FOUND THEN
|
|
543
|
+
RETURN QUERY SELECT 'current_template_not_found'::text, NULL::text;
|
|
544
|
+
RETURN;
|
|
545
|
+
END IF;
|
|
546
|
+
BEGIN
|
|
547
|
+
IF template_row.permission IS DISTINCT FROM 'read'
|
|
548
|
+
OR metadata_value->'resource_scopes'
|
|
549
|
+
IS DISTINCT FROM pg_catalog.to_jsonb(template_row.resource_scopes)
|
|
550
|
+
OR metadata_value->>'destination' IS DISTINCT FROM template_row.destination
|
|
551
|
+
OR (metadata_value->>'timeout_seconds')::integer
|
|
552
|
+
IS DISTINCT FROM template_row.timeout_seconds
|
|
553
|
+
OR metadata_value->>'idempotency_key_template'
|
|
554
|
+
IS DISTINCT FROM template_row.idempotency_key_template
|
|
555
|
+
OR metadata_value->>'expected_outcome_digest'
|
|
556
|
+
IS DISTINCT FROM template_row.expected_outcome_digest
|
|
557
|
+
OR metadata_value->>'source_hash' IS DISTINCT FROM template_row.source_hash
|
|
558
|
+
OR metadata_value->>'action_kind' IS DISTINCT FROM template_row.action_kind
|
|
559
|
+
OR (metadata_value->>'action_version')::integer
|
|
560
|
+
IS DISTINCT FROM template_row.action_version
|
|
561
|
+
OR metadata_value->>'action_contract_digest'
|
|
562
|
+
IS DISTINCT FROM template_row.action_contract_digest
|
|
563
|
+
OR (metadata_value->>'template_updated_at')::timestamptz
|
|
564
|
+
IS DISTINCT FROM template_row.updated_at
|
|
565
|
+
OR duration_ms_value > template_row.timeout_seconds * 1000 THEN
|
|
566
|
+
RETURN QUERY SELECT 'current_template_changed'::text, NULL::text;
|
|
567
|
+
RETURN;
|
|
568
|
+
END IF;
|
|
569
|
+
EXCEPTION WHEN OTHERS THEN
|
|
570
|
+
RETURN QUERY SELECT 'current_template_changed'::text, NULL::text;
|
|
571
|
+
RETURN;
|
|
572
|
+
END;
|
|
573
|
+
|
|
574
|
+
SELECT lease.*
|
|
575
|
+
INTO lease_row
|
|
576
|
+
FROM semicolony.cron_read_action_occurrence_leases AS lease
|
|
577
|
+
WHERE lease.idempotency_key = p_idempotency_key
|
|
578
|
+
FOR UPDATE;
|
|
579
|
+
|
|
580
|
+
IF NOT FOUND OR lease_row.claim_id IS DISTINCT FROM p_claim_id THEN
|
|
581
|
+
RETURN QUERY SELECT 'occurrence_lease_not_owned'::text, NULL::text;
|
|
582
|
+
RETURN;
|
|
583
|
+
END IF;
|
|
584
|
+
IF lease_row.state IS NULL
|
|
585
|
+
OR lease_row.state NOT IN ('acquired', 'completed')
|
|
586
|
+
OR (lease_row.state = 'acquired' AND lease_row.completed_at IS NOT NULL)
|
|
587
|
+
OR (lease_row.state = 'completed' AND lease_row.completed_at IS NULL) THEN
|
|
588
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
589
|
+
RETURN;
|
|
590
|
+
END IF;
|
|
591
|
+
|
|
592
|
+
SELECT outbox.*
|
|
593
|
+
INTO outbox_row
|
|
594
|
+
FROM semicolony.cron_read_action_relay_outbox AS outbox
|
|
595
|
+
WHERE outbox.idempotency_key = p_idempotency_key
|
|
596
|
+
FOR UPDATE;
|
|
597
|
+
outbox_exists := FOUND;
|
|
598
|
+
|
|
599
|
+
IF metadata_value->'relay_required' IS NOT DISTINCT FROM 'true'::jsonb THEN
|
|
600
|
+
IF NOT outbox_exists
|
|
601
|
+
OR outbox_row.request_digest IS DISTINCT FROM metadata_value->>'relay_request_digest'
|
|
602
|
+
OR outbox_row.state IS DISTINCT FROM 'completed'
|
|
603
|
+
OR outbox_row.outcome_status IS DISTINCT FROM 'delivered'
|
|
604
|
+
OR outbox_row.outcome_status IS DISTINCT FROM metadata_value->>'relay_outcome'
|
|
605
|
+
OR outbox_row.error_code IS NOT NULL
|
|
606
|
+
OR outbox_row.completed_at IS NULL THEN
|
|
607
|
+
RETURN QUERY SELECT 'relay_evidence_not_terminal'::text, NULL::text;
|
|
608
|
+
RETURN;
|
|
609
|
+
END IF;
|
|
610
|
+
ELSIF outbox_exists THEN
|
|
611
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
612
|
+
RETURN;
|
|
613
|
+
END IF;
|
|
614
|
+
|
|
615
|
+
SELECT evidence.*
|
|
616
|
+
INTO commitment_row
|
|
617
|
+
FROM semicolony.bot_commitments AS evidence
|
|
618
|
+
WHERE evidence.bot_id = 'semi'
|
|
619
|
+
AND evidence.source_type = 'cron'
|
|
620
|
+
AND evidence.runtime_source = 'hive-cron-v2'
|
|
621
|
+
AND evidence.metadata->>'cron_parity_runtime' = 'hive-cron-v2'
|
|
622
|
+
AND evidence.metadata->>'idempotency_key' = p_idempotency_key
|
|
623
|
+
FOR UPDATE;
|
|
624
|
+
commitment_exists := FOUND;
|
|
625
|
+
|
|
626
|
+
SELECT event.*
|
|
627
|
+
INTO event_row
|
|
628
|
+
FROM semicolony.commitment_events AS event
|
|
629
|
+
WHERE event.idempotency_key = 'cron:' || p_commitment_id
|
|
630
|
+
FOR UPDATE;
|
|
631
|
+
event_exists := FOUND;
|
|
632
|
+
|
|
633
|
+
expected_pipeline_context := pg_catalog.jsonb_build_object(
|
|
634
|
+
'job_id', p_job_id,
|
|
635
|
+
'job_name', job_row.name,
|
|
636
|
+
'schedule_expr', job_row.schedule_expr,
|
|
637
|
+
'scheduled_at', started_at_text
|
|
638
|
+
);
|
|
639
|
+
expected_commitment_metadata := metadata_value || pg_catalog.jsonb_build_object(
|
|
640
|
+
'output_digest', output_digest_value
|
|
641
|
+
);
|
|
642
|
+
expected_event_payload := pg_catalog.jsonb_build_object(
|
|
643
|
+
'run_status', status_value,
|
|
644
|
+
'duration_ms', duration_ms_value,
|
|
645
|
+
'job_id', p_job_id,
|
|
646
|
+
'job_name', job_row.name,
|
|
647
|
+
'schedule_expr', job_row.schedule_expr,
|
|
648
|
+
'error', NULL,
|
|
649
|
+
'output_digest', output_digest_value,
|
|
650
|
+
'occurrence_idempotency_key', p_idempotency_key
|
|
651
|
+
);
|
|
652
|
+
expected_title := 'cron: ' || job_row.name;
|
|
653
|
+
expected_description := 'schedule=' || job_row.schedule_expr
|
|
654
|
+
|| ' run_status=' || status_value
|
|
655
|
+
|| ' duration=' || duration_ms_value::text || 'ms';
|
|
656
|
+
|
|
657
|
+
IF lease_row.state IS NOT DISTINCT FROM 'completed' THEN
|
|
658
|
+
IF lease_row.completed_at IS DISTINCT FROM completed_at_value
|
|
659
|
+
OR NOT commitment_exists
|
|
660
|
+
OR NOT event_exists
|
|
661
|
+
OR commitment_row.id IS DISTINCT FROM p_commitment_id
|
|
662
|
+
OR commitment_row.bot_id IS DISTINCT FROM 'semi'
|
|
663
|
+
OR commitment_row.status IS DISTINCT FROM 'done'
|
|
664
|
+
OR commitment_row.title IS DISTINCT FROM expected_title
|
|
665
|
+
OR commitment_row.description IS DISTINCT FROM expected_description
|
|
666
|
+
OR commitment_row.source_type IS DISTINCT FROM 'cron'
|
|
667
|
+
OR commitment_row.source_ref IS DISTINCT FROM 'cron:' || p_job_id
|
|
668
|
+
OR commitment_row.deadline_at IS DISTINCT FROM NULL
|
|
669
|
+
OR commitment_row.steps IS DISTINCT FROM '[]'::jsonb
|
|
670
|
+
OR commitment_row.assigned_session IS DISTINCT FROM 'semi-cron-local'
|
|
671
|
+
OR commitment_row.session_owner IS DISTINCT FROM 'semi-cron-local'
|
|
672
|
+
OR commitment_row.pipeline_context IS DISTINCT FROM expected_pipeline_context
|
|
673
|
+
OR commitment_row.metadata IS DISTINCT FROM expected_commitment_metadata
|
|
674
|
+
OR commitment_row.last_heartbeat_at IS DISTINCT FROM completed_at_value
|
|
675
|
+
OR commitment_row.completed_at IS DISTINCT FROM completed_at_value
|
|
676
|
+
OR commitment_row.created_at IS DISTINCT FROM started_at_value
|
|
677
|
+
OR commitment_row.updated_at IS DISTINCT FROM completed_at_value
|
|
678
|
+
OR commitment_row.runtime_source IS DISTINCT FROM 'hive-cron-v2'
|
|
679
|
+
OR commitment_row.tenant_id IS DISTINCT FROM NULL
|
|
680
|
+
OR commitment_row.scope IS DISTINCT FROM 'platform-global'
|
|
681
|
+
OR commitment_row.install_id IS DISTINCT FROM NULL
|
|
682
|
+
OR event_row.commitment_id IS DISTINCT FROM p_commitment_id
|
|
683
|
+
OR event_row.event_type IS DISTINCT FROM 'cron_run_recorded'
|
|
684
|
+
OR event_row.payload IS DISTINCT FROM expected_event_payload
|
|
685
|
+
OR event_row.occurred_at IS DISTINCT FROM completed_at_value
|
|
686
|
+
OR event_row.bot_id IS DISTINCT FROM 'semi'
|
|
687
|
+
OR event_row.source_type IS DISTINCT FROM 'cron'
|
|
688
|
+
OR event_row.runtime_source IS DISTINCT FROM 'hive-cron-v2'
|
|
689
|
+
OR event_row.idempotency_key IS DISTINCT FROM 'cron:' || p_commitment_id
|
|
690
|
+
OR event_row.event_id IS NULL
|
|
691
|
+
OR event_row.event_id <= 0
|
|
692
|
+
OR event_row.recorded_at IS DISTINCT FROM completed_at_value THEN
|
|
693
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
694
|
+
RETURN;
|
|
695
|
+
END IF;
|
|
696
|
+
RETURN QUERY SELECT 'existing'::text, p_commitment_id;
|
|
697
|
+
RETURN;
|
|
698
|
+
END IF;
|
|
699
|
+
|
|
700
|
+
IF commitment_exists OR event_exists THEN
|
|
701
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
702
|
+
RETURN;
|
|
703
|
+
END IF;
|
|
704
|
+
|
|
705
|
+
BEGIN
|
|
706
|
+
INSERT INTO semicolony.bot_commitments (
|
|
707
|
+
id, bot_id, status, title, description, source_type, source_ref,
|
|
708
|
+
deadline_at, steps, assigned_session, session_owner, pipeline_context, metadata,
|
|
709
|
+
last_heartbeat_at, completed_at, created_at, updated_at, runtime_source,
|
|
710
|
+
tenant_id, scope, install_id
|
|
711
|
+
) VALUES (
|
|
712
|
+
p_commitment_id, 'semi', 'done', expected_title, expected_description,
|
|
713
|
+
'cron', 'cron:' || p_job_id, NULL, '[]'::jsonb,
|
|
714
|
+
'semi-cron-local', 'semi-cron-local', expected_pipeline_context,
|
|
715
|
+
expected_commitment_metadata, completed_at_value, completed_at_value,
|
|
716
|
+
started_at_value, completed_at_value, 'hive-cron-v2',
|
|
717
|
+
NULL, 'platform-global', NULL
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
INSERT INTO semicolony.commitment_events (
|
|
721
|
+
commitment_id, event_type, payload, occurred_at, recorded_at, bot_id, source_type,
|
|
722
|
+
runtime_source, idempotency_key
|
|
723
|
+
) VALUES (
|
|
724
|
+
p_commitment_id, 'cron_run_recorded', expected_event_payload,
|
|
725
|
+
completed_at_value, completed_at_value, 'semi', 'cron', 'hive-cron-v2',
|
|
726
|
+
'cron:' || p_commitment_id
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
IF stage_value = 'scheduled_primary' THEN
|
|
730
|
+
UPDATE semicolony.bot_cron_jobs AS job
|
|
731
|
+
SET last_run = completed_at_value,
|
|
732
|
+
last_status = 'success',
|
|
733
|
+
last_error = NULL,
|
|
734
|
+
consecutive_failures = 0
|
|
735
|
+
WHERE job.bot_id = 'semi'
|
|
736
|
+
AND job.job_id = p_job_id
|
|
737
|
+
AND job.runtime_source = 'hive-cron-v2';
|
|
738
|
+
GET DIAGNOSTICS affected_count = ROW_COUNT;
|
|
739
|
+
IF affected_count IS DISTINCT FROM 1 THEN
|
|
740
|
+
RAISE EXCEPTION 'evidence_state_conflict' USING ERRCODE = 'P0001';
|
|
741
|
+
END IF;
|
|
742
|
+
END IF;
|
|
743
|
+
|
|
744
|
+
UPDATE semicolony.cron_read_action_occurrence_leases AS lease
|
|
745
|
+
SET state = 'completed',
|
|
746
|
+
completed_at = completed_at_value
|
|
747
|
+
WHERE lease.idempotency_key = p_idempotency_key
|
|
748
|
+
AND lease.claim_id = p_claim_id
|
|
749
|
+
AND lease.state = 'acquired'
|
|
750
|
+
AND lease.completed_at IS NULL;
|
|
751
|
+
GET DIAGNOSTICS affected_count = ROW_COUNT;
|
|
752
|
+
IF affected_count IS DISTINCT FROM 1 THEN
|
|
753
|
+
RAISE EXCEPTION 'evidence_state_conflict' USING ERRCODE = 'P0001';
|
|
754
|
+
END IF;
|
|
755
|
+
EXCEPTION WHEN SQLSTATE 'P0001' THEN
|
|
756
|
+
RETURN QUERY SELECT 'evidence_state_conflict'::text, NULL::text;
|
|
757
|
+
RETURN;
|
|
758
|
+
END;
|
|
759
|
+
|
|
760
|
+
RETURN QUERY SELECT 'inserted'::text, p_commitment_id;
|
|
761
|
+
END;
|
|
762
|
+
$$;
|
|
763
|
+
|
|
764
|
+
REVOKE ALL ON FUNCTION semicolony.hive_cron_get_template(text)
|
|
765
|
+
FROM PUBLIC, sc_app, sc_provider;
|
|
766
|
+
REVOKE ALL ON FUNCTION semicolony.hive_cron_lock_authority(text, text, text)
|
|
767
|
+
FROM PUBLIC, sc_app, sc_provider;
|
|
768
|
+
REVOKE ALL ON FUNCTION semicolony.hive_cron_reconcile(text, text)
|
|
769
|
+
FROM PUBLIC, sc_app, sc_provider;
|
|
770
|
+
REVOKE ALL ON FUNCTION semicolony.hive_cron_write_evidence(text, text, text, jsonb)
|
|
771
|
+
FROM PUBLIC, sc_app, sc_provider;
|
|
772
|
+
|
|
773
|
+
DO $$
|
|
774
|
+
DECLARE
|
|
775
|
+
role_state record;
|
|
776
|
+
BEGIN
|
|
777
|
+
SELECT rolcanlogin, rolsuper, rolcreatedb, rolcreaterole,
|
|
778
|
+
rolinherit, rolreplication, rolbypassrls
|
|
779
|
+
INTO role_state
|
|
780
|
+
FROM pg_catalog.pg_roles
|
|
781
|
+
WHERE rolname = 'sc_provider';
|
|
782
|
+
|
|
783
|
+
IF NOT FOUND THEN
|
|
784
|
+
RAISE EXCEPTION 'sc_provider role missing; migration 135 required';
|
|
785
|
+
END IF;
|
|
786
|
+
|
|
787
|
+
IF NOT role_state.rolcanlogin
|
|
788
|
+
OR role_state.rolsuper
|
|
789
|
+
OR role_state.rolcreatedb
|
|
790
|
+
OR role_state.rolcreaterole
|
|
791
|
+
OR role_state.rolinherit
|
|
792
|
+
OR role_state.rolreplication
|
|
793
|
+
OR role_state.rolbypassrls THEN
|
|
794
|
+
RAISE EXCEPTION 'sc_provider role attributes are unsafe';
|
|
795
|
+
END IF;
|
|
796
|
+
|
|
797
|
+
END $$;
|
|
798
|
+
|
|
799
|
+
REVOKE ALL PRIVILEGES ON TABLE
|
|
800
|
+
semicolony.bot_cron_jobs,
|
|
801
|
+
semicolony.cron_capability_templates,
|
|
802
|
+
semicolony.cron_read_action_occurrence_leases,
|
|
803
|
+
semicolony.cron_read_action_relay_outbox,
|
|
804
|
+
semicolony.bot_commitments,
|
|
805
|
+
semicolony.commitment_events
|
|
806
|
+
FROM sc_provider;
|
|
807
|
+
|
|
808
|
+
REVOKE ALL PRIVILEGES
|
|
809
|
+
ON SEQUENCE semicolony.commitment_events_event_id_seq
|
|
810
|
+
FROM sc_provider;
|
|
811
|
+
|
|
812
|
+
GRANT EXECUTE ON FUNCTION semicolony.hive_cron_get_template(text) TO sc_provider;
|
|
813
|
+
GRANT EXECUTE ON FUNCTION semicolony.hive_cron_lock_authority(text, text, text) TO sc_provider;
|
|
814
|
+
GRANT EXECUTE ON FUNCTION semicolony.hive_cron_reconcile(text, text) TO sc_provider;
|
|
815
|
+
GRANT EXECUTE ON FUNCTION semicolony.hive_cron_write_evidence(text, text, text, jsonb)
|
|
816
|
+
TO sc_provider;
|