20260817_511_tenant_controlled_provision.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344
  1. """Provision the private tenant only through the dedicated control identity."""
  2. from alembic import op
  3. revision = "20260817_511"
  4. down_revision = "20260817_510"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(r"""
  9. CREATE FUNCTION public.tenant_control_provision(p_payload jsonb)
  10. RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path=pg_catalog,public AS $tenant_provision$
  11. DECLARE v_tenant text; v_principal uuid; v_host text; v_key text; v_digest text;
  12. BEGIN
  13. IF NOT pg_has_role(session_user,'dataops_tenant_control','MEMBER') THEN RAISE EXCEPTION 'tenant_control_identity_required'; END IF;
  14. IF jsonb_typeof(p_payload)<>'object' OR NOT (p_payload ?& ARRAY['tenant_id','principal_id','host','idempotency_key']) OR p_payload-ARRAY['tenant_id','principal_id','host','idempotency_key']<>'{}'::jsonb THEN RAISE EXCEPTION 'tenant_payload_closed'; END IF;
  15. v_tenant:=p_payload->>'tenant_id'; v_host:=p_payload->>'host'; v_key:=p_payload->>'idempotency_key';
  16. IF v_tenant !~ '^[a-z][a-z0-9-]{0,62}$' 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 v_host !~ '^[a-z0-9][a-z0-9.-]{0,252}$' OR v_host<>lower(v_host) OR v_key !~ '^[A-Za-z0-9._:-]{1,120}$' THEN RAISE EXCEPTION 'tenant_payload_invalid'; END IF;
  17. v_principal:=(p_payload->>'principal_id')::uuid; v_digest:=encode(sha256(convert_to(p_payload::text,'utf8')),'hex');
  18. INSERT INTO public.tenants(tenant_id,delivery_mode,state) VALUES(v_tenant,'private_single_tenant','active') ON CONFLICT(tenant_id) DO NOTHING;
  19. PERFORM set_config('dataops.tenant_id',v_tenant,true);
  20. INSERT INTO public.tenant_quotas(tenant_id,quota_name,limit_units) VALUES
  21. (v_tenant,'records',1),(v_tenant,'storage_objects',1),(v_tenant,'background_tasks',1),(v_tenant,'events',1),(v_tenant,'models',1),(v_tenant,'plugins',1),(v_tenant,'connectors',1),(v_tenant,'api_concurrency',1)
  22. ON CONFLICT(tenant_id,quota_name) DO NOTHING;
  23. INSERT INTO public.tenant_control_memberships(tenant_id,principal_id,host) VALUES(v_tenant,v_principal,v_host) ON CONFLICT(principal_id,host) DO UPDATE SET tenant_id=EXCLUDED.tenant_id;
  24. INSERT INTO public.tenant_audit_events(tenant_id,event_type,payload_digest,lease_fence) VALUES(v_tenant,'tenant.provision',v_digest,0);
  25. RETURN jsonb_build_object('tenant_id',v_tenant,'state','active','delivery_mode','private_single_tenant');
  26. END; $tenant_provision$;
  27. ALTER FUNCTION public.tenant_control_provision(jsonb) OWNER TO dataops_tenant_foundation_owner;
  28. REVOKE ALL ON FUNCTION public.tenant_control_provision(jsonb) FROM PUBLIC,dataops_app_runtime;
  29. GRANT EXECUTE ON FUNCTION public.tenant_control_provision(jsonb) TO dataops_tenant_control;
  30. """)
  31. def downgrade() -> None:
  32. op.execute("""
  33. DO $$ BEGIN
  34. IF EXISTS (SELECT 1 FROM public.tenants LIMIT 1) THEN RAISE EXCEPTION 'tenant provision downgrade refused while tenant data exists'; END IF;
  35. END $$;
  36. REVOKE EXECUTE ON FUNCTION public.tenant_control_provision(jsonb) FROM dataops_tenant_control;
  37. DROP FUNCTION public.tenant_control_provision(jsonb);
  38. """)