Skip to content

fix!: require merchant_authorization in AP2-active checkout responses#466

Open
sakinaroufid wants to merge 3 commits into
Universal-Commerce-Protocol:mainfrom
sakinaroufid:fix/ap2-merchant-authorization-required
Open

fix!: require merchant_authorization in AP2-active checkout responses#466
sakinaroufid wants to merge 3 commits into
Universal-Commerce-Protocol:mainfrom
sakinaroufid:fix/ap2-merchant-authorization-required

Conversation

@sakinaroufid
Copy link
Copy Markdown
Contributor

@sakinaroufid sakinaroufid commented May 21, 2026

Description

The spec mandates ap2.merchant_authorization in three places:

  • ap2-mandates.md:112 — "The business MUST include ap2.merchant_authorization in all checkout responses."
  • ap2-mandates.md:146-148 - "Businesses MUST embed their signature in the checkout response body under ap2.merchant_authorization..."
  • ap2-mandates.md:216-218 — "The checkout mandate MUST contain the full checkout response including the ap2.merchant_authorization field."

But ap2_with_merchant_authorization had no required array, so a response that omits the field validates clean. A spec-driven implementer ships a business that emits non-AP2-conformant responses, breaking the cryptographic chain at the platform's mandate-verification step — only discoverable when payment processors reject the mandate downstream.

The chain works only if every link is present:

  1. Business signs the checkout body → JWS in ap2.merchant_authorization
  2. Platform verifies, issues an SD-JWT+kb mandate that covers the full checkout including merchant_authorization
  3. Business verifies the mandate at completion, expecting its own signature embedded in the signed-over content

Omitting step 1 makes step 3's verification fail by design.

Follows the established response-only-required pattern in checkout.json (ucp, id, status, currency, totals, links — all combine ucp_request: "omit" with inclusion in the parent required array).

Category (Required)

  • Capability: New schemas (Discovery, Cart, etc.) or extensions. (Requires Maintainer approval)

Checklist

  • I have followed the Contributing Guide (including Conventional Commits title requirements and ! for breaking changes).
  • I have updated the documentation (if applicable).
  • My changes pass all local linting and formatting checks.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • (For Core/Capability) I have included/updated the relevant JSON schemas.
  • I have regenerated Python Pydantic models by running generate_models.sh under python_sdk.

Screenshots / Logs (if applicable)

Local verification:

  • ucp-schema lint source/ — 88 files passed
  • scripts/validate_examples.py --schema-base source/schemas/ — 253 passed, 0 failed (every existing AP2 spec example already includes merchant_authorization)
  • scripts/test_validate_examples.py — 43 passed
  • ucp-schema resolve ap2_mandate.json --response --op read confirms the resolved response schema has required: ["merchant_authorization"]
  • ucp-schema resolve ap2_mandate.json --request --op create confirms request variants still strip merchant_authorization correctly via ucp_request: "omit"

Breaking-change note

AP2-active business responses that previously validated without merchant_authorization will now fail schema validation. That is the intended behavior per spec. Downstream impact:

  • The conformance suite (ap2_test.py) primarily exercises the request side, so it should be unaffected; any mock business responses elsewhere that omit merchant_authorization will need to add it.
  • python-sdk regenerates Pydantic models from schemas, so the field becomes non-Optional on the next regen.

The spec mandates the field in three places (ap2-mandates.md:112, 146, 216):
"The business MUST include ap2.merchant_authorization in all checkout
responses." But ap2_with_merchant_authorization had no `required` array, so
schema-driven implementations could omit the field and validate clean —
breaking the AP2 cryptographic chain silently:

1. Business signs the checkout → puts JWS in ap2.merchant_authorization
2. Platform verifies, issues SD-JWT+kb mandate covering the full checkout
   *including* merchant_authorization
3. Business later verifies the mandate, expecting its own signature embedded

If step 1 omits merchant_authorization, step 2's mandate has nothing for the
business to recognize in step 3, and the non-repudiation guarantee is gone.

Matches the established response-only-required pattern in checkout.json
(ucp/id/status/currency/totals/links: ucp_request=omit + listed in required).

Breaking: AP2-active business responses that previously validated without
merchant_authorization now fail schema validation. This is the intended
behavior per spec; mock servers in the conformance suite may need a parallel
update to include the field.
@sakinaroufid sakinaroufid requested review from a team as code owners May 21, 2026 05:01
@TateLyman
Copy link
Copy Markdown

Source-read note: this looks directionally right, but I think it may only close the ap2: {} case, not the missing-namespace case.

ap2_with_merchant_authorization now has required: ["merchant_authorization"], so an AP2 object without the signature should fail. But in dev.ucp.shopping.checkout, the ap2 property itself is still only declared under properties and is not listed in a parent required array. Under normal JSON Schema semantics, a checkout response with all base checkout required fields and no ap2 object can still satisfy the extension allOf because absent optional properties are not validated.

That seems to leave the same AP2-active response gap described in the PR body: the docs say once dev.ucp.shopping.ap2_mandate is negotiated, the business MUST include ap2.merchant_authorization in all checkout responses, but a schema-driven mock/business could still omit the entire ap2 namespace unless the resolved response schema also requires ap2.

Would it make sense to require ap2 at the extension composition level for the response variant as well, while preserving the request-side create/update: omit behavior? If the custom ucp-schema resolve --response --op read already injects required: ["ap2"] from the extension negotiation context, then this is just worth capturing in the PR validation note; I could not see that guarantee from the source schema alone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants