Skip to content

fix(rest/nodejs): send Cache-Control on discovery and payment_handlers in the checkout envelope - #168

Open
vishkaty wants to merge 2 commits into
Universal-Commerce-Protocol:mainfrom
vishkaty:fix-nodejs-04-08-conformance
Open

fix(rest/nodejs): send Cache-Control on discovery and payment_handlers in the checkout envelope#168
vishkaty wants to merge 2 commits into
Universal-Commerce-Protocol:mainfrom
vishkaty:fix-nodejs-04-08-conformance

Conversation

@vishkaty

@vishkaty vishkaty commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

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/ucp sends no Cache-Control header (only content-type).

Expected: overview.md (lines 1055-1057) — profile responses MUST include Cache-Control with public and max-age of at least 60s, and MUST NOT use private/no-store/no-cache.

Fix: src/api/discovery.ts sets Cache-Control: public, max-age=3600 on 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 ucp envelope 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, whose allOf adds required: ["payment_handlers"] to the ucp envelope.

Fix: src/api/checkout.ts includes payment_handlers in the ucp envelope, matching the Python reference which emits payment_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 against response_checkout_schema under ajv 2020-12; stripping the field reproduces the oracle failure.

Note for maintainers: the underlying gap is also in @ucp-js/sdk — its ExtendedCheckoutResponse.ucp type does not model payment_handlers (the Python SDK does), and it is a Zod strip object, so the sample declares the envelope explicitly to preserve the field. Adding payment_handlers to the SDK's response_checkout_schema would be the durable upstream fix; I am happy to send that to js-sdk as a follow-up.

Verification

  • Each fix has a failing-first test (test/discovery.test.ts, test/lifecycle.test.ts) that is red on current code and green after, each kill-tested by reverting the one line.
  • npm run build (tsc) clean; npm test 119 pass / 0 fail (116 baseline + 3 new); pinned pre-commit (prettier, codespell) clean.
  • Why CI missed them: the discovery test never asserts response headers, and the checkout tests never schema-validate the ucp envelope.

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.
@damaz91 damaz91 added the status:needs-triage Signal that the PR is ready for human triage label Aug 5, 2026
@carolinerg1 carolinerg1 added devops status:under-review and removed status:needs-triage Signal that the PR is ready for human triage labels Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants