fix(rest/nodejs): answer checkout errors with the UCP error envelope - #174
Open
vishkaty wants to merge 1 commit into
Open
fix(rest/nodejs): answer checkout errors with the UCP error envelope#174vishkaty wants to merge 1 commit into
vishkaty wants to merge 1 commit into
Conversation
Checkout business and protocol failures returned a flat {detail} JSON
body. The 2026-04-08 spec requires the UCP error envelope for business
failures (checkout.md: the response contains ucp.status "error" with
messages describing the failure) and code plus content in the body for
protocol errors (checkout-rest.md), and the Python reference already
answers this way through its typed exception taxonomy
(rest/python/server/exceptions.py + the ucp_exception_handler).
Add the same taxonomy for Node (src/utils/ucp_error.ts) and convert
every checkout error site to it, mirroring the Python codes:
IDEMPOTENCY_CONFLICT 409, RESOURCE_NOT_FOUND 404,
CHECKOUT_NOT_MODIFIABLE 409, OUT_OF_STOCK 400 (409 at complete,
matching the Python complete path), INVALID_REQUEST 400, and the
payment family INSUFFICIENT_FUNDS 402 / FRAUD_DETECTED 403 /
UNKNOWN_TOKEN with severity requires_buyer_input. HTTP statuses are
preserved at every site; this is a response shape change only.
validateInventory now throws the typed OutOfStockError so the
create and update catch blocks can render it distinctly from other
validation failures.
Left as is, matching the Python reference which answers the same way:
the testing simulation endpoints, the signature 401 wrapper, and the
internal 500 in completeCheckout.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Observed vs expected
The Node reference answers checkout business and protocol failures with a flat
{"detail": "..."}body. The Python reference answers the same failures with the UCP error envelope through its typed exception taxonomy (rest/python/server/exceptions.py+ theucp_exception_handlerinserver.py).Reproduced on the wire at current main (
5237750), both cases against a freshly booted server:Idempotency-Keywith a different create body:409 {"detail":"Idempotency key reused with different parameters"}with nocodeand nocontent.checkout-rest.md(Protocol errors) expects the JSON body to carrycodeandcontent.gardenias):400 {"detail":"Insufficient stock for item gardenias"}with noucpenvelope.checkout.md(Error Handling) expectsucp.status: "error"withmessages[]describing the failure. The Python server self tests exactly this shape (server/integration_test.py, out of stock assertsucp.status == "error").Fix
New
src/utils/ucp_error.tsmirrors the Python taxonomy, and every checkout error site converts to it. HTTP statuses are preserved at every site; this is a response shape change only.IDEMPOTENCY_CONFLICTIdempotencyConflictErrorRESOURCE_NOT_FOUNDResourceNotFoundErrorCHECKOUT_NOT_MODIFIABLECheckoutNotModifiableErrorvalidateInventorythrow; reservation at complete)OUT_OF_STOCKOutOfStockError(default 400; complete path raises 409)INVALID_REQUESTInvalidRequestErrorINSUFFICIENT_FUNDS/FRAUD_DETECTED/UNKNOWN_TOKEN, severityrequires_buyer_inputPaymentFailedErrorDeliberately left as is: the
signature.ts401 wrapper (its own documented shape, identical in both languages), the internal 500 incompleteCheckout(Python leaves unhandled errors to the plain FastAPI 500 as well), and thetesting.tssimulation endpoints. One asymmetry there worth naming honestly: the Simulation-Secret 403 is flatHTTPExceptiondetail in Python too, but Python does envelope the order-not-found 404 on simulate shipping (it flows throughResourceNotFoundError), whiletesting.tsanswers it flat. Converting the simulation surface felt like scope creep for this change, so it is left for a follow-up.Disclosed status delta kept, not changed:
UNKNOWN_TOKENstays 400 here while Python answers 402; aligning statuses felt like a separate decision from fixing the shape.Verification
test/error_envelope.test.ts(6 tests, one per code family) written first and failing 0/6 against the old shape, green after the change.npm test),tscclean. Two existing tests asserting the old flat shape (validation_flow.test.ts,fulfillment.test.ts) now assert the envelope code and content, which strengthens them.ucp.version,messages[].type/code/content/severity).Why CI did not catch it: the existing tests asserted the flat shape as the expected one (those assertions came from #138, my own earlier behavioral suite, so this PR also corrects my own tests to the spec shape), and the fork test job runs only after maintainer approval.
Note: #171 touches the neighboring idempotency hash computation in the same functions; the changes do not overlap semantically, and I am happy to rebase whichever lands second.