Skip to content

test: negotiate AP2 before mandate conformance checks - #115

Open
aeoess wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
aeoess:test/ap2-negotiation
Open

aeoess wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
aeoess:test/ap2-negotiation

Conversation

@aeoess

@aeoess aeoess commented Sep 29, 2026

Copy link
Copy Markdown

Description

Refs #114.

test_ap2_mandate_completion sent the placeholder mandate header.payload.signature~kb_signature and expected the checkout to complete. The mock Platform profile never declared AP2, so AP2 was never negotiated, and a Business that ignores mandates passed the only AP2 test.

This PR makes the AP2 tests run only when AP2 is actually negotiated, and adds the missing negative case.

  • Opt-in AP2 Platform profile. The mock agent server serves a second profile, /profiles/shopping-agent-ap2.json, that declares checkout, the AP2 Mandates extension extending checkout, and a public P-256 signing key for the trusted platform provider flow. The default profile is unchanged, because a Business that supports AP2 must refuse every completion without a mandate once AP2 is negotiated, which would affect every other test. Any existing keys in the profile are preserved.
  • Negotiation computed, not assumed. capability_intersection() implements the UCP intersection algorithm: name match, highest mutual version, and repeated pruning of extensions whose parent is absent. The AP2 tests skip unless AP2 is in that intersection. A Business that merely lists AP2 in a shape that does not negotiate is skipped, not tested.
  • New negative case. test_complete_without_mandate_is_rejected requires that completion without ap2.checkout_mandate does not complete and carries mandate_required.
  • Positive case skipped. test_ap2_mandate_completion now skips with an explicit reason. Once AP2 is negotiated, a Business that verifies the mandate must reject the placeholder, so the test cannot stay a passing positive. A real positive needs a signed SD-JWT+kb mandate fixture and a signing model, which this PR does not attempt.

Release scope

AP2 names changed between UCP releases. In 2026-04-08 the capability is dev.ucp.shopping.ap2_mandate, with /specification/ap2-mandates, /specification/checkout and top-level signing_keys. In 2026-08-25, and in current HEAD at the time of this investigation, the capability is dev.ucp.common.payment.ap2_mandate, with the payment extension paths, /specification/shopping/checkout and keys. #114 cited the newer name from the spec at ucp@4a3aa10f. This PR follows the suite's current 2026-04-08 target. ap2_release() describes that release only and returns None for others, in which case the tests skip.

Not in this PR

  • No AP2 cryptographic verification (signature, key binding, expiry, terms match).
  • No change to samples. The current sample server does not declare AP2, so both AP2 tests skip against it.

Open question for review

The 2026-04-08 AP2 spec does not bind mandate_required to one error form. The test accepts either, each in its own shape: HTTP 200 requires the UCP error envelope (ucp.status = error) with an error message whose code is mandate_required, and any other status requires a top-level code of mandate_required. Happy to narrow this if you prefer one form.

How it was tested

Against the Python sample server from samples@c2e04403, with python-sdk@v2026-04-08-6 as pinned in CI and the flower shop data. V1 to V4 used local, unsubmitted modifications of the sample to exercise both outcomes.

Variant positive new negative
V0: unmodified sample skipped (not negotiated) skipped (not negotiated)
V1: sample declares AP2, does not enforce it skipped fails, checkout completes without a mandate
V2: sample negotiates AP2 and returns mandate_required skipped passes
V3a: Platform profile without the checkout parent skipped (AP2 pruned) skipped (AP2 pruned)
V4: Business declares AP2 at a version the Platform lacks skipped (no mutual version) skipped (no mutual version)

Full suite against the unmodified sample: 73 passed, 3 skipped, 2 failed, compared with 74 passed, 1 skipped, 2 failed without this change. The two failures are order_test.py::OrderTest::test_order_fulfillment_retrieval and test_order_update, which return HTTP 500 with and without this change. The difference is the two AP2 tests now skipping. ruff check and ruff format --check pass.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation. N/A, the change is test code only.
  • My changes generate no new warnings
  • 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. Two pre-existing order_test failures occur with and without this change, see above.
  • Any dependent changes have been merged and published in downstream modules. N/A, no dependent changes.

@google-cla

google-cla Bot commented Sep 29, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@damaz91 damaz91 added the status:needs-triage Signal that the PR is ready for human triage label Sep 29, 2026
The AP2 test sent a placeholder mandate and expected completion, but the
mock Platform profile never declared AP2, so AP2 was never negotiated and
a Business that ignores mandates passed.

Serve an opt-in Platform profile that declares checkout, the AP2 Mandates
extension and a signing key, and run the AP2 tests only when AP2 is in the
capability intersection computed per the UCP intersection algorithm. Add a
negative case that completion without a checkout mandate is refused with
mandate_required. Skip the positive case until a signed SD-JWT+kb mandate
fixture exists, since a verifying Business must reject the placeholder.

AP2 names, URLs and the key field differ between UCP releases, so the
descriptor covers only the suite's current 2026-04-08 target.

Refs Universal-Commerce-Protocol#114
@aeoess
aeoess force-pushed the test/ap2-negotiation branch from 5516246 to 18f1c96 Compare October 9, 2026 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:needs-triage Signal that the PR is ready for human triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants