fix(rest/nodejs): send Cache-Control on discovery and payment_handlers in the checkout envelope - #168
Open
vishkaty wants to merge 2 commits into
Open
Conversation
The `/.well-known/ucp` merchant profile response omitted the `Cache-Control` header. `getMerchantProfile` in `src/api/discovery.ts` returned `c.json(...)` with only the content-type set, so `curl -sI` on the running server shows no caching directive. overview.md (Discovery) makes this a MUST: "Profile responses MUST include a Cache-Control header with `public` and `max-age` of at least 60 seconds. Profiles MUST NOT be served with `private`, `no-store`, or `no-cache` directives." (docs/specification/overview.md). Observed: response headers carry only `content-type: application/json`. Expected: `Cache-Control: public, max-age>=60`. This is the Node twin of the merged Python fix (samples#153), which added the same header to the Python discovery route; mirror its `public, max-age=3600`. Why their CI did not catch it: the Node discovery test asserts only the JSON registries, never the response headers.
`CheckoutService.createCheckout` built the response `ucp` envelope with only
`{ version, capabilities }`. That envelope is persisted with the checkout, so
every checkout response path that reads it back (get, update, complete, cancel,
and the idempotent-replay branches) also omitted `payment_handlers`.
The 04-08 schema binds checkout responses to
`ucp.json#/$defs/response_checkout_schema`, whose `allOf` adds
`required: ["payment_handlers"]` to the ucp envelope. Validating a live create
response against that schema fails with
`must have required property 'payment_handlers'` (pointer `/ucp`).
Observed: `ucp = { version, capabilities }`.
Expected: `ucp = { version, capabilities, payment_handlers }`, where
`payment_handlers` is a (possibly empty) object. This mirrors the Python
reference, which constructs `ResponseCheckout(..., payment_handlers={})`.
Fix: emit `payment_handlers: {}` in the ucp envelope at construction. Because
the envelope is stored on the checkout, the single construction site covers all
five response paths. The envelope is declared as a standalone object so the
extra property reaches the wire even though the JS SDK response type does not
yet model it.
Why their CI did not catch it: the Node checkout tests assert status and body
fields but never schema-validate the ucp envelope.
carolinerg1
approved these changes
Aug 6, 2026
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.
Two independent 2026-04-08 conformance fixes in the Node reference server, both surfaced by validating the live Node responses against the official ucp-schema oracle and cross-checking against the Python reference. Separate commits, each independently revertible.
1. Discovery profile omits Cache-Control (commit 1)
Observed:
GET /.well-known/ucpsends noCache-Controlheader (onlycontent-type).Expected: overview.md (lines 1055-1057) — profile responses MUST include
Cache-Controlwithpublicandmax-ageof at least 60s, and MUST NOT useprivate/no-store/no-cache.Fix:
src/api/discovery.tssetsCache-Control: public, max-age=3600on the profile response — the Node twin of the merged Python fix #153, which added the same header (same value) to the Python discovery only.2. Checkout response ucp envelope omits required payment_handlers (commit 2)
Observed: the
ucpenvelope on checkout responses contains only{version, capabilities}. The official validator on a live Node create response fails with/ucp: must have required property 'payment_handlers'.Expected: the 04-08 checkout response binds to
ucp.json#/$defs/response_checkout_schema, whoseallOfaddsrequired: ["payment_handlers"]to the ucp envelope.Fix:
src/api/checkout.tsincludespayment_handlersin the ucp envelope, matching the Python reference which emitspayment_handlers={}. The envelope is constructed once and persisted, so create/get/update/complete/cancel and the idempotent replay all carry it (verified on the wire). The fixed create response validates VALID againstresponse_checkout_schemaunder ajv 2020-12; stripping the field reproduces the oracle failure.Note for maintainers: the underlying gap is also in
@ucp-js/sdk— itsExtendedCheckoutResponse.ucptype does not modelpayment_handlers(the Python SDK does), and it is a Zodstripobject, so the sample declares the envelope explicitly to preserve the field. Addingpayment_handlersto the SDK'sresponse_checkout_schemawould be the durable upstream fix; I am happy to send that to js-sdk as a follow-up.Verification
npm run build(tsc) clean;npm test119 pass / 0 fail (116 baseline + 3 new); pinned pre-commit (prettier, codespell) clean.