From ec016d1bd86664112561bd1c7f5fd5a0009ada2e Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:01:57 -0700 Subject: [PATCH] fix(policy_engine): decide config policy suppression from fresh db query only --- .../proxy/policy_engine/policy_endpoints.py | 2 +- .../test_policy_engine_endpoints.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/policy_engine/policy_endpoints.py b/litellm/proxy/policy_engine/policy_endpoints.py index 718223da5d8..cff1378c676 100644 --- a/litellm/proxy/policy_engine/policy_endpoints.py +++ b/litellm/proxy/policy_engine/policy_endpoints.py @@ -136,7 +136,7 @@ async def list_policies(version_status: Optional[str] = None): [ _config_policy_to_db_response(policy_name, policy) for policy_name, policy in registry.list_config_policies().items() - if policy_name not in db_policy_names and registry.get_source(policy_name) != "db" + if policy_name not in db_policy_names ] if include_config else [] diff --git a/tests/test_litellm/proxy/policy_engine/test_policy_engine_endpoints.py b/tests/test_litellm/proxy/policy_engine/test_policy_engine_endpoints.py index 9c486540b3d..1ca830dc1e6 100644 --- a/tests/test_litellm/proxy/policy_engine/test_policy_engine_endpoints.py +++ b/tests/test_litellm/proxy/policy_engine/test_policy_engine_endpoints.py @@ -159,6 +159,33 @@ class TestListPoliciesIncludesConfig: db_entry = next(p for p in response.policies if p.definition_location == "db") assert db_entry.version_status == "draft" + @pytest.mark.asyncio + async def test_stale_registry_provenance_does_not_hide_config_policy(self, policy_registry, monkeypatch): + """ + Another proxy instance can delete or demote the production DB override + between registry syncs. The endpoint's fresh DB query is the source of + truth for conflicts; stale in-memory provenance from the last sync must + not suppress the config entry once no production override exists. + """ + policy_registry.load_policies({"shared-name": {"guardrails": {"add": ["config-guard"]}}}) + production_row = _make_policy_row(policy_id="uuid-1", policy_name="shared-name", guardrails_add=["db-guard"]) + sync_prisma = MagicMock() + sync_prisma.db.litellm_policytable.find_many = AsyncMock(side_effect=[[production_row], []]) + await policy_registry.sync_policies_from_db(sync_prisma) + assert policy_registry.get_source("shared-name") == "db" + + fresh_prisma = MagicMock() + fresh_prisma.db.litellm_policytable.find_many = AsyncMock(return_value=[]) + _set_prisma(monkeypatch, fresh_prisma) + + response = await policy_endpoints.list_policies() + + assert response.total_count == 1 + entry = response.policies[0] + assert entry.policy_name == "shared-name" + assert entry.definition_location == "config" + assert entry.guardrails_add == ["config-guard"] + @pytest.mark.asyncio async def test_version_status_filter_excludes_config_policies(self, policy_registry, monkeypatch): row = _make_policy_row(policy_id="uuid-1", policy_name="db-policy", version_status="draft")