| 123456789101112131415161718192021222324252627282930313233343536373839 |
- """Permit claim-bound lifecycle recovery while retaining active-only quota claims."""
- from alembic import op
- revision = "20260817_509"
- down_revision = "20260817_508"
- branch_labels = None
- depends_on = None
- def upgrade() -> None:
- op.execute(r"""
- CREATE OR REPLACE FUNCTION public.tenant_control_issue_claim(p_payload jsonb)
- RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $tenant_claim_issue$
- 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;
- BEGIN
- IF NOT pg_has_role(session_user,'dataops_tenant_control','MEMBER') THEN RAISE EXCEPTION 'tenant_control_identity_required'; END IF;
- 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;
- 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;
- 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';
- 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.delivery_mode='private_single_tenant' AND ((v_action IN ('quota_reserve','quota_settle','quota_release') AND t.state='active') OR (v_action='lifecycle_transition' AND t.state<>'deleted'));
- IF NOT FOUND THEN RAISE EXCEPTION 'tenant_membership_denied'; END IF;
- v_digest:=encode(sha256(convert_to(v_request::text,'utf8')),'hex'); v_expires:=clock_timestamp()+interval '120 seconds';
- 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);
- 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);
- RETURN jsonb_build_object('claim_uid',v_claim,'tenant_id',v_tenant,'action',v_action,'request_digest',v_digest,'expires_at',v_expires);
- END; $tenant_claim_issue$;
- ALTER FUNCTION public.tenant_control_issue_claim(jsonb) OWNER TO dataops_tenant_foundation_owner;
- REVOKE ALL ON FUNCTION public.tenant_control_issue_claim(jsonb) FROM PUBLIC,dataops_app_runtime;
- GRANT EXECUTE ON FUNCTION public.tenant_control_issue_claim(jsonb) TO dataops_tenant_control;
- """)
- def downgrade() -> None:
- op.execute("""
- DO $$ BEGIN
- IF EXISTS (SELECT 1 FROM public.tenant_control_claims c JOIN public.tenants t ON t.tenant_id=c.tenant_id WHERE c.action='lifecycle_transition' AND t.state<>'active') THEN RAISE EXCEPTION 'lifecycle claim state-gate downgrade refused while non-active claims exist'; END IF;
- END $$;
- """)
|