From 0e5c29c7d4865b7362eba87b3690216b5e43d0fb Mon Sep 17 00:00:00 2001 From: Vishal Katyal Date: Wed, 22 Jul 2026 11:16:42 -0400 Subject: [PATCH] fix(rest/python): return 422 for version_unsupported The overview.md error registry maps version_unsupported to REST 422, but the version-negotiation check in dependencies.py raised HTTP 400. Return 422 so a platform can distinguish a negotiation failure from a generic bad request. Adds a test asserting a future UCP-Agent version yields 422 with the structured negotiation error. --- rest/python/server/dependencies.py | 3 ++- rest/python/server/integration_test.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/rest/python/server/dependencies.py b/rest/python/server/dependencies.py index ebb49355..c8d15e75 100644 --- a/rest/python/server/dependencies.py +++ b/rest/python/server/dependencies.py @@ -82,7 +82,8 @@ async def validate_ucp_headers(ucp_agent: str): if agent_version > server_version: raise HTTPException( - status_code=400, + # version_unsupported maps to REST 422 in the overview.md error registry. + status_code=422, detail={ "status": "error", "errors": [ diff --git a/rest/python/server/integration_test.py b/rest/python/server/integration_test.py index d91fc56d..dfb4249e 100644 --- a/rest/python/server/integration_test.py +++ b/rest/python/server/integration_test.py @@ -425,6 +425,29 @@ def test_missing_ucp_agent_header(self) -> None: # default validation) self.assertEqual(response.status_code, 422) + def test_unsupported_version_returns_422(self) -> None: + """A future protocol version is rejected with 422 version_unsupported. + + The error registry in overview.md maps `version_unsupported` to REST 422. + """ + with self.client: + payload = self._create_checkout_payload( + "test_checkout_bad_version", [("rose", "Red Rose", 1000, 1)] + ) + headers = self._get_headers(idempotency_key="ver", request_id="ver") + headers["UCP-Agent"] = ( + 'profile="https://agent.example/profile"; version="2999-01-01"' + ) + response = self.client.post( + "/checkout-sessions", + headers=headers, + json=payload.model_dump(mode="json", exclude_none=True), + ) + self.assertEqual(response.status_code, 422, f"Response: {response.text}") + # It is the structured negotiation error, not a generic validation 422. + error = response.json()["detail"]["errors"][0] + self.assertEqual(error["severity"], "critical") + def test_discount_code_matches_case_insensitively(self) -> None: """Codes are matched case-insensitively by business (discount.md)."""