20260817_507_private_delivery_claim_gate.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. """Keep conditional tenant control private-only until external activation approval."""
  2. from alembic import op
  3. revision = "20260817_507"
  4. down_revision = "20260817_506"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(
  9. r"""
  10. CREATE OR REPLACE FUNCTION public.tenant_control_issue_claim(p_payload jsonb)
  11. RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $tenant_claim_issue$
  12. DECLARE v_principal uuid; v_host text; v_action text; v_tenant text; v_claim uuid; v_nonce uuid; v_request jsonb; v_digest text; v_expires timestamptz;
  13. BEGIN
  14. IF NOT pg_has_role(session_user,'dataops_tenant_control','MEMBER') THEN RAISE EXCEPTION 'tenant_control_identity_required'; END IF;
  15. IF jsonb_typeof(p_payload)<>'object' OR NOT (p_payload ?& ARRAY['claim_uid','nonce','principal_id','host','action','request']) OR p_payload-ARRAY['claim_uid','nonce','principal_id','host','action','request']<>'{}'::jsonb THEN RAISE EXCEPTION 'tenant_payload_closed'; END IF;
  16. IF coalesce(p_payload->>'claim_uid','') !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' OR coalesce(p_payload->>'nonce','') !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' OR coalesce(p_payload->>'principal_id','') !~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$' OR p_payload->>'host' !~ '^[a-z0-9][a-z0-9.-]{0,252}$' OR p_payload->>'host'<>lower(p_payload->>'host') OR p_payload->>'action' NOT IN ('quota_reserve','quota_settle','quota_release','lifecycle_transition') OR jsonb_typeof(p_payload->'request')<>'object' THEN RAISE EXCEPTION 'tenant_payload_invalid'; END IF;
  17. v_claim:=(p_payload->>'claim_uid')::uuid; v_nonce:=(p_payload->>'nonce')::uuid; v_principal:=(p_payload->>'principal_id')::uuid; v_host:=p_payload->>'host'; v_action:=p_payload->>'action'; v_request:=p_payload->'request';
  18. SELECT m.tenant_id INTO v_tenant FROM public.tenant_control_memberships m JOIN public.tenants t ON t.tenant_id=m.tenant_id WHERE m.principal_id=v_principal AND m.host=v_host AND t.state='active' AND t.delivery_mode='private_single_tenant';
  19. IF NOT FOUND THEN RAISE EXCEPTION 'tenant_membership_denied'; END IF;
  20. v_digest:=encode(sha256(convert_to(v_request::text,'utf8')),'hex'); v_expires:=clock_timestamp()+interval '120 seconds';
  21. INSERT INTO public.tenant_control_claims(claim_uid,nonce,tenant_id,principal_id,host,action,request,request_digest,expires_at) VALUES(v_claim,v_nonce,v_tenant,v_principal,v_host,v_action,v_request,v_digest,v_expires);
  22. PERFORM set_config('dataops.tenant_id',v_tenant,true); INSERT INTO public.tenant_audit_events(tenant_id,event_type,payload_digest,lease_fence) VALUES(v_tenant,'tenant.claim.issued',v_digest,0);
  23. RETURN jsonb_build_object('claim_uid',v_claim,'tenant_id',v_tenant,'action',v_action,'request_digest',v_digest,'expires_at',v_expires);
  24. END; $tenant_claim_issue$;
  25. ALTER FUNCTION public.tenant_control_issue_claim(jsonb) OWNER TO dataops_tenant_foundation_owner;
  26. REVOKE ALL ON FUNCTION public.tenant_control_issue_claim(jsonb) FROM PUBLIC,dataops_app_runtime;
  27. GRANT EXECUTE ON FUNCTION public.tenant_control_issue_claim(jsonb) TO dataops_tenant_control;
  28. """
  29. )
  30. def downgrade() -> None:
  31. op.execute(
  32. """
  33. DO $$ BEGIN
  34. IF EXISTS (SELECT 1 FROM public.tenant_control_claims c JOIN public.tenants t ON t.tenant_id=c.tenant_id WHERE t.delivery_mode<>'private_single_tenant') THEN
  35. RAISE EXCEPTION 'private delivery gate downgrade refused while non-private claims exist';
  36. END IF;
  37. END $$;
  38. """
  39. )