20260813_499_runtime_control_approval.py 12 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. """Close WP09 recovery and canary approvals behind one-shot database facts."""
  2. from alembic import op
  3. revision = "20260813_499"
  4. down_revision = "20260813_498"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(r"""
  9. CREATE TABLE public.agent_runtime_control_events (
  10. event_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  11. tenant_id text NOT NULL,
  12. idempotency_key text NOT NULL,
  13. request_digest char(64) NOT NULL,
  14. payload_digest char(64) NOT NULL,
  15. operation text NOT NULL CHECK (operation IN ('recover','canary_promote','canary_rollback')),
  16. approval_task_uid uuid NOT NULL REFERENCES public.governance_tasks(uid),
  17. result jsonb NOT NULL,
  18. created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
  19. UNIQUE(tenant_id,idempotency_key)
  20. );
  21. ALTER TABLE public.agent_runtime_control_events OWNER TO dataops_agent_runtime_owner;
  22. REVOKE ALL ON TABLE public.agent_runtime_control_events FROM PUBLIC,dataops_agent_runtime;
  23. GRANT SELECT,INSERT,UPDATE ON TABLE public.agent_runtime_control_events TO dataops_agent_runtime_owner;
  24. CREATE FUNCTION public.agent_runtime_control(p_payload jsonb)
  25. RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $control$
  26. DECLARE t record; s record; c record; event record; v_result jsonb; v_fence bigint; v_payload_digest text;
  27. BEGIN
  28. IF jsonb_typeof(p_payload)<>'object' OR NOT (p_payload ?& ARRAY['tenant_id','agent_uid','principal_id','business_domain_uid','environment','operation','approval_task_uid','idempotency_key','request_digest','expected_fence','incident_uid','from_state','to_state','generation','from_generation','to_generation','dataset_version','dataset_digest','metrics_digest','threshold_policy','issued_at']) OR p_payload-ARRAY['tenant_id','agent_uid','principal_id','business_domain_uid','environment','operation','approval_task_uid','idempotency_key','request_digest','expected_fence','incident_uid','from_state','to_state','generation','from_generation','to_generation','dataset_version','dataset_digest','metrics_digest','threshold_policy','issued_at']<>'{}'::jsonb THEN RAISE EXCEPTION 'runtime control payload is closed'; END IF;
  29. IF (p_payload->>'agent_uid') !~ '^[0-9a-f-]{36}$' OR (p_payload->>'principal_id') !~ '^[0-9a-f-]{36}$' OR (p_payload->>'business_domain_uid') !~ '^[0-9a-f-]{36}$' OR (p_payload->>'approval_task_uid') !~ '^[0-9a-f-]{36}$' OR (p_payload->>'request_digest') !~ '^[0-9a-f]{64}$' OR (p_payload->>'expected_fence') !~ '^[0-9]+$' OR (p_payload->>'issued_at') !~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:.+-]+$' OR p_payload->>'operation' NOT IN ('recover','canary_promote','canary_rollback') OR (p_payload->>'environment') !~ '^[A-Za-z0-9_-]{1,40}$' OR (p_payload->>'idempotency_key') !~ '^[A-Za-z0-9_.:-]{1,160}$' OR (p_payload->>'dataset_version') !~ '^[A-Za-z0-9._-]{1,120}$' OR (p_payload->>'dataset_digest') !~ '^[0-9a-f]{64}$' OR (p_payload->>'metrics_digest') !~ '^[0-9a-f]{64}$' OR (p_payload->>'threshold_policy') !~ '^[A-Za-z0-9._-]{1,120}$' THEN RAISE EXCEPTION 'runtime control value invalid'; END IF;
  30. v_payload_digest:=encode(sha256(convert_to(p_payload::text,'utf8')),'hex');
  31. SELECT * INTO event FROM public.agent_runtime_control_events WHERE tenant_id=p_payload->>'tenant_id' AND idempotency_key=p_payload->>'idempotency_key' FOR UPDATE;
  32. IF FOUND THEN IF event.request_digest<>p_payload->>'request_digest' OR event.operation<>p_payload->>'operation' OR event.payload_digest<>v_payload_digest THEN RAISE EXCEPTION 'control replay conflict'; END IF; RETURN event.result || jsonb_build_object('replay',true); END IF;
  33. SELECT * INTO t FROM public.governance_tasks WHERE uid=(p_payload->>'approval_task_uid')::uuid FOR UPDATE;
  34. IF NOT FOUND THEN RAISE EXCEPTION 'approval context denied'; END IF;
  35. IF t.status<>'approved' THEN RAISE EXCEPTION 'approval already consumed'; END IF;
  36. IF t.subject_uid<>(p_payload->>'agent_uid') OR t.business_domain_uid<>(p_payload->>'business_domain_uid') OR t.due_at<=clock_timestamp() OR t.context->>'tenant_id'<>p_payload->>'tenant_id' OR t.context->>'agent_uid'<>p_payload->>'agent_uid' OR t.context->>'principal_id'<>p_payload->>'principal_id' OR t.context->>'business_domain_uid'<>p_payload->>'business_domain_uid' OR t.context->>'environment'<>p_payload->>'environment' OR t.context->>'control_operation'<>p_payload->>'operation' OR t.context->>'request_digest'<>p_payload->>'request_digest' OR t.context->>'expected_fence'<>p_payload->>'expected_fence' OR t.context->>'issued_at'<>p_payload->>'issued_at' OR t.context->>'from_state'<>p_payload->>'from_state' OR t.context->>'to_state'<>p_payload->>'to_state' OR t.context->>'generation'<>p_payload->>'generation' OR t.context->>'from_generation'<>p_payload->>'from_generation' OR t.context->>'to_generation'<>p_payload->>'to_generation' OR t.context->>'dataset_version'<>p_payload->>'dataset_version' OR t.context->>'dataset_digest'<>p_payload->>'dataset_digest' OR t.context->>'metrics_digest'<>p_payload->>'metrics_digest' OR t.context->>'threshold_policy'<>p_payload->>'threshold_policy' OR COALESCE(t.context->>'incident_uid','')<>COALESCE(p_payload->>'incident_uid','') THEN RAISE EXCEPTION 'approval context denied'; END IF;
  37. IF NOT EXISTS(SELECT 1 FROM public.governance_task_reviews r WHERE r.task_uid=t.uid AND r.decision='approve' AND r.reviewer_uid<>(p_payload->>'principal_id')::uuid) THEN RAISE EXCEPTION 'approval reviewer denied'; END IF;
  38. IF p_payload->>'operation'='recover' THEN
  39. IF (p_payload->>'incident_uid') !~ '^[0-9a-f-]{36}$' OR p_payload->>'from_state'<>'paused' OR p_payload->>'to_state'<>'active' THEN RAISE EXCEPTION 'recover control invalid'; END IF;
  40. SELECT * INTO s FROM public.agent_runtime_states WHERE tenant_id=p_payload->>'tenant_id' FOR UPDATE;
  41. IF NOT FOUND OR s.state<>p_payload->>'from_state' OR s.lease_fence<>(p_payload->>'expected_fence')::bigint OR s.incident_ref<>(p_payload->>'incident_uid') THEN RAISE EXCEPTION 'runtime state fence conflict'; END IF;
  42. UPDATE public.agent_runtime_states SET state='active',approval_ref=t.uid::text,lease_fence=lease_fence+1,updated_at=clock_timestamp() WHERE tenant_id=s.tenant_id AND lease_fence=s.lease_fence RETURNING lease_fence INTO v_fence;
  43. ELSE
  44. IF COALESCE(p_payload->>'incident_uid','')<>'' OR p_payload->>'generation'<>p_payload->>'from_generation' OR p_payload->>'generation'<>p_payload->>'to_generation' OR p_payload->>'to_state'<>(CASE WHEN p_payload->>'operation'='canary_promote' THEN 'approved' ELSE 'rolled_back' END) THEN RAISE EXCEPTION 'canary control invalid'; END IF;
  45. SELECT * INTO c FROM public.agent_generation_canaries WHERE tenant_id=p_payload->>'tenant_id' AND generation=p_payload->>'generation' FOR UPDATE;
  46. IF NOT FOUND OR c.status<>p_payload->>'from_state' OR c.lease_fence<>(p_payload->>'expected_fence')::bigint THEN RAISE EXCEPTION 'generation fence conflict'; END IF;
  47. UPDATE public.agent_generation_canaries SET status=p_payload->>'to_state',approval_ref=t.uid::text,lease_fence=lease_fence+1 WHERE tenant_id=c.tenant_id AND generation=c.generation AND lease_fence=c.lease_fence RETURNING lease_fence INTO v_fence;
  48. END IF;
  49. UPDATE public.governance_tasks SET status='closed',closed_at=clock_timestamp(),updated_at=clock_timestamp() WHERE uid=t.uid AND status='approved';
  50. IF NOT FOUND THEN RAISE EXCEPTION 'approval already consumed'; END IF;
  51. v_result:=jsonb_build_object('operation',p_payload->>'operation','lease_fence',v_fence,'approval_task_uid',t.uid::text,'replay',false);
  52. INSERT INTO public.agent_runtime_control_events(tenant_id,idempotency_key,request_digest,payload_digest,operation,approval_task_uid,result) VALUES(p_payload->>'tenant_id',p_payload->>'idempotency_key',p_payload->>'request_digest',v_payload_digest,p_payload->>'operation',t.uid,v_result);
  53. RETURN v_result;
  54. END; $control$;
  55. ALTER FUNCTION public.agent_runtime_control(jsonb) OWNER TO dataops_agent_runtime_owner;
  56. REVOKE ALL ON FUNCTION public.agent_runtime_control(jsonb) FROM PUBLIC;
  57. GRANT EXECUTE ON FUNCTION public.agent_runtime_control(jsonb) TO dataops_agent_runtime,dataops_agent_runtime_owner;
  58. CREATE OR REPLACE FUNCTION public.agent_runtime_transition(p_tenant text,p_state text,p_incident uuid,p_approval uuid,p_expected_fence bigint) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $deprecated_transition$ BEGIN RAISE EXCEPTION 'deprecated runtime transition; use agent_runtime_control'; END; $deprecated_transition$;
  59. CREATE OR REPLACE FUNCTION public.agent_runtime_canary_transition(p_tenant text,p_generation text,p_status text,p_approval uuid,p_expected_fence bigint,p_metrics jsonb,p_dataset_version text) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $deprecated_canary$ BEGIN RAISE EXCEPTION 'deprecated runtime canary transition; use agent_runtime_control'; END; $deprecated_canary$;
  60. REVOKE ALL ON FUNCTION public.agent_runtime_transition(text,text,uuid,uuid,bigint) FROM PUBLIC,dataops_agent_runtime;
  61. REVOKE ALL ON FUNCTION public.agent_runtime_canary_transition(text,text,text,uuid,bigint,jsonb,text) FROM PUBLIC,dataops_agent_runtime;
  62. ALTER FUNCTION public.agent_runtime_transition(text,text,uuid,uuid,bigint) OWNER TO dataops_agent_runtime_owner;
  63. ALTER FUNCTION public.agent_runtime_canary_transition(text,text,text,uuid,bigint,jsonb,text) OWNER TO dataops_agent_runtime_owner;
  64. """)
  65. def downgrade() -> None:
  66. op.execute("""
  67. REVOKE ALL ON FUNCTION public.agent_runtime_control(jsonb) FROM PUBLIC;
  68. DROP FUNCTION IF EXISTS public.agent_runtime_control(jsonb);
  69. DROP TABLE IF EXISTS public.agent_runtime_control_events;
  70. CREATE OR REPLACE FUNCTION public.agent_runtime_transition(p_tenant text,p_state text,p_incident uuid,p_approval uuid,p_expected_fence bigint) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $old_transition$ DECLARE v_fence bigint; BEGIN IF p_state NOT IN ('degraded','paused','active') THEN RAISE EXCEPTION 'runtime state invalid'; END IF; IF p_state='active' AND (p_approval IS NULL OR NOT EXISTS(SELECT 1 FROM governance_tasks WHERE uid=p_approval AND status='approved' AND due_at>clock_timestamp())) THEN RAISE EXCEPTION 'runtime recovery approval required'; END IF; IF p_state IN ('degraded','paused') AND p_incident IS NULL THEN RAISE EXCEPTION 'runtime incident required'; END IF; UPDATE agent_runtime_states SET state=p_state,incident_ref=COALESCE(p_incident::text,incident_ref),approval_ref=p_approval::text,lease_fence=lease_fence+1,updated_at=clock_timestamp() WHERE tenant_id=p_tenant AND lease_fence=p_expected_fence RETURNING lease_fence INTO v_fence; IF NOT FOUND THEN RAISE EXCEPTION 'runtime state fence conflict'; END IF; RETURN v_fence; END; $old_transition$;
  71. CREATE OR REPLACE FUNCTION public.agent_runtime_canary_transition(p_tenant text,p_generation text,p_status text,p_approval uuid,p_expected_fence bigint,p_metrics jsonb,p_dataset_version text) RETURNS bigint LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $old_canary$ DECLARE v_fence bigint; BEGIN IF p_status NOT IN ('approved','rolled_back') OR p_dataset_version !~ '^[A-Za-z0-9._-]{1,120}$' OR jsonb_typeof(p_metrics)<>'object' THEN RAISE EXCEPTION 'canary evidence invalid'; END IF; IF p_approval IS NULL OR NOT EXISTS(SELECT 1 FROM governance_tasks WHERE uid=p_approval AND status='approved' AND due_at>clock_timestamp()) THEN RAISE EXCEPTION 'canary approval required'; END IF; UPDATE agent_generation_canaries SET status=p_status,approval_ref=p_approval::text,lease_fence=lease_fence+1 WHERE tenant_id=p_tenant AND generation=p_generation AND lease_fence=p_expected_fence RETURNING lease_fence INTO v_fence; IF NOT FOUND THEN RAISE EXCEPTION 'generation fence conflict'; END IF; RETURN v_fence; END; $old_canary$;
  72. GRANT EXECUTE ON FUNCTION public.agent_runtime_transition(text,text,uuid,uuid,bigint),public.agent_runtime_canary_transition(text,text,text,uuid,bigint,jsonb,text) TO dataops_agent_runtime,dataops_agent_runtime_owner;
  73. """)