@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.
@@ -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;